summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java41
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java20
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java746
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java115
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java48
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java60
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java14
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java63
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java25
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java7
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java5
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java338
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java102
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java13
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java8
-rw-r--r--libs/WindowManager/Shell/Android.bp43
-rw-r--r--libs/WindowManager/Shell/OWNERS1
-rw-r--r--libs/WindowManager/Shell/aconfig/OWNERS3
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig50
-rw-r--r--libs/WindowManager/Shell/multivalentTests/Android.bp97
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml13
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidTest.xml31
-rw-r--r--libs/WindowManager/Shell/multivalentTests/OWNERS4
-rw-r--r--libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt520
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt215
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt92
l---------libs/WindowManager/Shell/multivalentTestsForDevice1
l---------libs/WindowManager/Shell/multivalentTestsForDeviceless1
-rw-r--r--libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/circular_progress.xml33
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml (renamed from libs/WindowManager/Shell/res/drawable/pip_split.xml)19
-rw-r--r--libs/WindowManager/Shell/res/drawable/rounded_button.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml6
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml48
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/maximize_menu_button.xml38
-rw-r--r--libs/WindowManager/Shell/res/layout/pip_menu.xml10
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml27
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml1
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml16
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml61
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java)7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java237
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java111
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java251
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java305
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt130
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java114
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl)7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java)14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java)22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java166
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java389
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt437
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java101
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java114
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java214
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java277
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java577
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java141
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java600
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java235
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java614
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java321
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java225
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java)58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java215
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java552
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java352
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt)26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt191
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java602
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt121
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java111
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt171
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt118
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt169
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt144
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java113
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt127
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java110
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt74
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java138
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt171
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt113
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java84
-rw-r--r--libs/androidfw/Android.bp11
-rw-r--r--libs/androidfw/ApkAssets.cpp5
-rw-r--r--libs/androidfw/AssetManager2.cpp20
-rw-r--r--libs/androidfw/BackupHelpers.cpp10
-rw-r--r--libs/androidfw/Idmap.cpp15
-rw-r--r--libs/androidfw/LoadedArsc.cpp25
-rw-r--r--libs/androidfw/ResourceTypes.cpp95
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h5
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h12
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h7
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h52
-rw-r--r--libs/androidfw/include/androidfw/misc.h2
-rw-r--r--libs/androidfw/misc.cpp20
-rw-r--r--libs/hostgraphics/ADisplay.cpp159
-rw-r--r--libs/hostgraphics/Android.bp12
-rw-r--r--libs/hostgraphics/OWNERS4
-rw-r--r--libs/hostgraphics/gui/Surface.h2
-rw-r--r--libs/hwui/Android.bp64
-rw-r--r--libs/hwui/AutoBackendTextureRelease.cpp6
-rw-r--r--libs/hwui/CanvasTransform.cpp7
-rw-r--r--libs/hwui/DeviceInfo.h9
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/FeatureFlags.h8
-rw-r--r--libs/hwui/FrameInfoVisualizer.cpp2
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp15
-rw-r--r--libs/hwui/JankTracker.cpp7
-rw-r--r--libs/hwui/ProfileData.cpp2
-rw-r--r--libs/hwui/Properties.cpp12
-rw-r--r--libs/hwui/Properties.h11
-rw-r--r--libs/hwui/RecordingCanvas.cpp15
-rw-r--r--libs/hwui/RecordingCanvas.h2
-rw-r--r--libs/hwui/RenderProperties.h27
-rw-r--r--libs/hwui/SkiaCanvas.cpp15
-rw-r--r--libs/hwui/SkiaCanvas.h2
-rw-r--r--libs/hwui/VectorDrawable.cpp8
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig21
-rw-r--r--libs/hwui/apex/android_paint.cpp25
-rw-r--r--libs/hwui/apex/include/android/graphics/paint.h14
-rw-r--r--libs/hwui/hwui/Bitmap.cpp6
-rw-r--r--libs/hwui/hwui/Canvas.h2
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h62
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp10
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp12
-rw-r--r--libs/hwui/hwui/MinikinUtils.h2
-rw-r--r--libs/hwui/hwui/Paint.h5
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp36
-rw-r--r--libs/hwui/jni/Graphics.cpp12
-rw-r--r--libs/hwui/jni/GraphicsJNI.h2
-rw-r--r--libs/hwui/jni/Paint.cpp32
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp125
-rw-r--r--libs/hwui/libhwui.map.txt1
-rw-r--r--libs/hwui/pipeline/skia/ATraceMemoryDump.cpp26
-rw-r--r--libs/hwui/pipeline/skia/ATraceMemoryDump.h3
-rw-r--r--libs/hwui/pipeline/skia/DumpOpsCanvas.h5
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp20
-rw-r--r--libs/hwui/platform/android/thread/ThreadBase.h (renamed from libs/hwui/thread/ThreadBase.h)6
-rw-r--r--libs/hwui/platform/host/ProfileDataContainer.cpp40
-rw-r--r--libs/hwui/platform/host/Readback.cpp50
-rw-r--r--libs/hwui/platform/host/WebViewFunctorManager.cpp75
-rw-r--r--libs/hwui/platform/host/renderthread/CacheManager.cpp64
-rw-r--r--libs/hwui/platform/host/renderthread/RenderThread.cpp141
-rw-r--r--libs/hwui/platform/host/thread/ThreadBase.h76
-rw-r--r--libs/hwui/private/hwui/WebViewFunctor.h8
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp36
-rw-r--r--libs/hwui/renderthread/EglManager.cpp21
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp3
-rw-r--r--libs/hwui/renderthread/RenderThread.h3
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp92
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp9
-rw-r--r--libs/hwui/tests/common/CallCountingCanvas.h6
-rw-r--r--libs/hwui/tests/unit/CanvasOpTests.cpp2
-rw-r--r--libs/hwui/tests/unit/FatalTestCanvas.h4
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp5
-rw-r--r--libs/hwui/tests/unit/SkiaPipelineTests.cpp10
-rw-r--r--libs/hwui/utils/ForceDark.h6
-rw-r--r--libs/hwui/utils/HostColorSpace.cpp417
-rw-r--r--libs/input/PointerController.cpp18
-rw-r--r--libs/input/PointerController.h10
-rw-r--r--libs/input/SpriteIcon.cpp3
-rw-r--r--libs/input/SpriteIcon.h34
-rw-r--r--libs/input/TouchSpotController.cpp2
-rw-r--r--libs/input/tests/PointerController_test.cpp4
613 files changed, 16345 insertions, 6514 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 65955b1d9bcc..e37dea4dfd69 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -126,7 +126,7 @@ public final class CommonFoldingFeature {
* @see #FEATURE_PATTERN
* @return {@link List} of {@link CommonFoldingFeature}.
*/
- static List<CommonFoldingFeature> parseListFromString(@NonNull String value,
+ public static List<CommonFoldingFeature> parseListFromString(@NonNull String value,
@State int hingeState) {
List<CommonFoldingFeature> features = new ArrayList<>();
String[] featureStrings = value.split(";");
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index a184dff5005b..88fd461debbe 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -36,6 +36,7 @@ import androidx.window.util.BaseDataProducer;
import com.android.internal.R;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -78,7 +79,9 @@ public final class DeviceStateManagerFoldingFeatureProducer
private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
@NonNull
- private final BaseDataProducer<String> mRawFoldSupplier;
+ private final RawFoldingFeatureProducer mRawFoldSupplier;
+
+ private final boolean mIsHalfOpenedSupported;
private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
@Override
@@ -101,10 +104,12 @@ public final class DeviceStateManagerFoldingFeatureProducer
};
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
- @NonNull BaseDataProducer<String> rawFoldSupplier) {
+ @NonNull RawFoldingFeatureProducer rawFoldSupplier,
+ @NonNull DeviceStateManager deviceStateManager) {
mRawFoldSupplier = rawFoldSupplier;
String[] deviceStatePosturePairs = context.getResources()
.getStringArray(R.array.config_device_state_postures);
+ boolean isHalfOpenedSupported = false;
for (String deviceStatePosturePair : deviceStatePosturePairs) {
String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
if (deviceStatePostureMapping.length != 2) {
@@ -128,12 +133,13 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
continue;
}
-
+ isHalfOpenedSupported = isHalfOpenedSupported
+ || posture == CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
mDeviceStateToPostureMap.put(deviceState, posture);
}
-
+ mIsHalfOpenedSupported = isHalfOpenedSupported;
if (mDeviceStateToPostureMap.size() > 0) {
- Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
+ Objects.requireNonNull(deviceStateManager)
.registerCallback(context.getMainExecutor(), mDeviceStateCallback);
}
}
@@ -188,6 +194,31 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
/**
+ * Returns a {@link List} of all the {@link CommonFoldingFeature} with the state set to
+ * {@link CommonFoldingFeature#COMMON_STATE_UNKNOWN}. This method parses a {@link String} so a
+ * caller should consider caching the value or the derived value.
+ */
+ @NonNull
+ public List<CommonFoldingFeature> getFoldsWithUnknownState() {
+ Optional<String> optionalFoldingFeatureString = mRawFoldSupplier.getCurrentData();
+
+ if (optionalFoldingFeatureString.isPresent()) {
+ return CommonFoldingFeature.parseListFromString(
+ optionalFoldingFeatureString.get(), CommonFoldingFeature.COMMON_STATE_UNKNOWN
+ );
+ }
+ return Collections.emptyList();
+ }
+
+
+ /**
+ * Returns {@code true} if the device supports half-opened mode, {@code false} otherwise.
+ */
+ public boolean isHalfOpenedSupported() {
+ return mIsHalfOpenedSupported;
+ }
+
+ /**
* Adds the data to the storeFeaturesConsumer when the data is ready.
* @param storeFeaturesConsumer a consumer to collect the data when it is first available.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index ed99501b867d..6714263ad952 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -20,6 +20,7 @@ import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -55,7 +56,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 4;
+ return 5;
}
@NonNull
@@ -64,6 +65,11 @@ public class WindowExtensionsImpl implements WindowExtensions {
}
@NonNull
+ private DeviceStateManager getDeviceStateManager() {
+ return Objects.requireNonNull(getApplication().getSystemService(DeviceStateManager.class));
+ }
+
+ @NonNull
private DeviceStateManagerFoldingFeatureProducer getFoldingFeatureProducer() {
if (mFoldingFeatureProducer == null) {
synchronized (mLock) {
@@ -73,7 +79,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
new RawFoldingFeatureProducer(context);
mFoldingFeatureProducer =
new DeviceStateManagerFoldingFeatureProducer(context,
- foldingFeatureProducer);
+ foldingFeatureProducer, getDeviceStateManager());
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index ca3d8d18db83..80afb16d5832 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
@@ -356,6 +357,13 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.addTaskFragmentOperation(fragmentToken, operation);
}
+ void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, boolean dimOnTask) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_DIM_ON_TASK).setDimOnTask(dimOnTask).build();
+ wct.addTaskFragmentOperation(fragmentToken, operation);
+ }
+
void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
}
@@ -374,9 +382,13 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
- return new TaskFragmentAnimationParams.Builder()
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
- .build();
+ final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(colorBackground.getColor())
+ .build();
+ } else {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
deleted file mode 100644
index ff49cdcab349..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static java.util.Objects.requireNonNull;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * The parameter to create an overlay container that retrieved from
- * {@link android.app.ActivityOptions} bundle.
- */
-class OverlayCreateParams {
-
- // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS =
- "androidx.window.extensions.OverlayCreateParams";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
- "androidx.window.extensions.OverlayCreateParams.taskId";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
- "androidx.window.extensions.OverlayCreateParams.tag";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
- "androidx.window.extensions.OverlayCreateParams.bounds";
-
- private final int mTaskId;
-
- @NonNull
- private final String mTag;
-
- @NonNull
- private final Rect mBounds;
-
- OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
- mTaskId = taskId;
- mTag = requireNonNull(tag);
- mBounds = requireNonNull(bounds);
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- @NonNull
- String getTag() {
- return mTag;
- }
-
- @NonNull
- Rect getBounds() {
- return mBounds;
- }
-
- @Override
- public int hashCode() {
- int result = mTaskId;
- result = 31 * result + mTag.hashCode();
- result = 31 * result + mBounds.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (!(obj instanceof OverlayCreateParams thatParams)) return false;
- return mTaskId == thatParams.mTaskId
- && mTag.equals(thatParams.mTag)
- && mBounds.equals(thatParams.mBounds);
- }
-
- @Override
- public String toString() {
- return OverlayCreateParams.class.getSimpleName() + ": {"
- + "taskId=" + mTaskId
- + ", tag=" + mTag
- + ", bounds=" + mBounds
- + "}";
- }
-
- /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
- @Nullable
- static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
- final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
- if (paramsBundle == null) {
- return null;
- }
- final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
- final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
- final Rect bounds = requireNonNull(paramsBundle.getParcelable(
- KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
-
- return new OverlayCreateParams(taskId, tag, bounds);
- }
-}
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 08b7bb89d10c..39cfacec8447 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -201,7 +201,7 @@ class SplitContainer {
return null;
}
return new SplitInfo(primaryActivityStack, secondaryActivityStack,
- mCurrentSplitAttributes, mToken);
+ mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mToken));
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
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 4973a4d85af7..1abda4287800 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -34,24 +35,28 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHA
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_ACTIVITY_STACK_TOKEN;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
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.RESULT_EXPAND_FAILED_NO_TF_INFO;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
+import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
import android.app.Instrumentation;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -62,12 +67,14 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemProperties;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
@@ -84,6 +91,7 @@ import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsImpl;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.core.util.function.Function;
+import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -96,6 +104,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
/**
* Main controller class that manages split states and presentation.
@@ -136,6 +145,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
/**
+ * A calculator function to compute {@link ActivityStack} attributes in a task, which is called
+ * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ mActivityStackAttributesCalculator;
+
+ /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
* When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -148,8 +166,34 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Callback to Jetpack to notify about changes to split states. */
@GuardedBy("mLock")
@Nullable
- private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private Consumer<List<SplitInfo>> mSplitInfoCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
+
+ /**
+ * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks}
+ * and corresponding {@link Executor executors} to dispatch the callback.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks =
+ new ArrayMap<>();
+
+ private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
+
+ /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
+ mEmbeddedActivityWindowInfoCallback;
+
+ /** Listener registered to {@link ClientTransactionListenerController}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
+ Flags.activityWindowInfoFlag()
+ ? this::onActivityWindowInfoChanged
+ : null;
+
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
@@ -273,7 +317,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void unpinTopActivityStack(int taskId){
+ public void unpinTopActivityStack(int taskId) {
synchronized (mLock) {
Log.i(TAG, "Request to unpin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
@@ -319,13 +363,35 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void setActivityStackAttributesCalculator(
+ @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ calculator) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = calculator;
+ }
+ }
+
+ @Override
+ public void clearActivityStackAttributesCalculator() {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = null;
+ }
+ }
+
@GuardedBy("mLock")
@Nullable
Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
return mSplitAttributesCalculator;
}
- @Override
+ // TODO(b/295993745): remove after we migrate to the bundle approach.
@NonNull
public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
@NonNull IBinder token) {
@@ -342,6 +408,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Registers the split organizer callback to notify about changes to active splits.
+ *
* @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
* {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
*/
@@ -355,12 +422,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Registers the split organizer callback to notify about changes to active splits.
+ *
* @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
*/
+ @Override
public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
synchronized (mLock) {
- mEmbeddingCallback = callback;
- updateCallbackIfNecessary();
+ mSplitInfoCallback = callback;
+ updateSplitInfoCallbackIfNecessary();
}
}
@@ -370,7 +439,35 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void clearSplitInfoCallback() {
synchronized (mLock) {
- mEmbeddingCallback = null;
+ mSplitInfoCallback = null;
+ }
+ }
+
+ /**
+ * Registers the callback for the {@link ActivityStack} state change.
+ *
+ * @param executor The executor to dispatch the callback.
+ * @param callback The callback for this {@link ActivityStack} state change.
+ */
+ @Override
+ public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<List<ActivityStack>> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ synchronized (mLock) {
+ mActivityStackCallbacks.put(callback, executor);
+ updateActivityStackCallbackIfNecessary();
+ }
+ }
+
+ /** @see #registerActivityStackCallback(Executor, Consumer) */
+ @Override
+ public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) {
+ Objects.requireNonNull(callback);
+
+ synchronized (mLock) {
+ mActivityStackCallbacks.remove(callback);
}
}
@@ -382,13 +479,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
// Translate ActivityStack to TaskFragmentContainer.
final List<TaskFragmentContainer> pendingFinishingContainers =
- activityStackTokens.stream()
- .map(token -> {
+ activityStackTokens.stream().map(token -> {
synchronized (mLock) {
return getContainer(token);
}
- }).filter(Objects::nonNull)
- .toList();
+ }).filter(Objects::nonNull).toList();
if (pendingFinishingContainers.isEmpty()) {
return;
@@ -471,6 +566,71 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void updateActivityStackAttributes(@NonNull ActivityStack.Token activityStackToken,
+ @NonNull ActivityStackAttributes attributes) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
+ Objects.requireNonNull(activityStackToken);
+ Objects.requireNonNull(attributes);
+
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
+ if (container == null) {
+ Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken);
+ return;
+ }
+ if (!container.isOverlay()) {
+ Log.w(TAG, "Updating non-overlay container has not supported yet!");
+ return;
+ }
+
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ParentContainerInfo getParentContainerInfo(
+ @NonNull ActivityStack.Token activityStackToken) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return null;
+ }
+ Objects.requireNonNull(activityStackToken);
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
+ if (container == null) {
+ return null;
+ }
+ final TaskContainer.TaskProperties properties = container.getTaskContainer()
+ .getTaskProperties();
+ return mPresenter.createParentContainerInfoFromTaskProperties(properties);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ActivityStack.Token getActivityStackToken(@NonNull String tag) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return null;
+ }
+ Objects.requireNonNull(tag);
+ synchronized (mLock) {
+ final TaskFragmentContainer taskFragmentContainer =
+ getContainer(container -> tag.equals(container.getOverlayTag()));
+ if (taskFragmentContainer == null) {
+ return null;
+ }
+ return ActivityStack.Token.createFromBinder(taskFragmentContainer
+ .getTaskFragmentToken());
+ }
+ }
+
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
@@ -539,8 +699,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when a TaskFragment is created and organized by this organizer.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is created.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is created.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -548,7 +709,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentInfo taskFragmentInfo) {
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
@@ -568,8 +729,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when the status of an organized TaskFragment is changed.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is changed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -639,8 +801,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when an organized TaskFragment is removed.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is removed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is removed.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -660,14 +823,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Called when the parent leaf Task of organized TaskFragments is changed.
* When the leaf Task is changed, the organizer may want to update the TaskFragments in one
* transaction.
- *
+ * <p>
* For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
* with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
* can be an override bounds.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskId Id of the parent Task that is changed.
- * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
+ * @param taskId Id of the parent Task that is changed.
+ * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -678,14 +841,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
return;
}
+ // Checks if container should be updated before apply new parentInfo.
+ final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
taskContainer.updateTaskFragmentParentInfo(parentInfo);
- if (!taskContainer.isVisible()) {
- // Don't update containers if the task is not visible. We only update containers when
- // parentInfo#isVisibleRequested is true.
- return;
- }
- if (isInPictureInPicture(parentInfo.getConfiguration())) {
- // No need to update presentation in PIP until the Task exit PIP.
+
+ // If the last direct activity of the host task is dismissed and the overlay container is
+ // the only taskFragment, the overlay container should also be dismissed.
+ dismissOverlayContainerIfNeeded(wct, taskContainer);
+
+ if (!shouldUpdateContainer) {
return;
}
updateContainersInTask(wct, taskContainer);
@@ -720,20 +884,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskId The Task that the activity is reparented to.
- * @param activityIntent The intent that the activity is original launched with.
- * @param activityToken If the activity belongs to the same process as the organizer, this
- * will be the actual activity token; if the activity belongs to a
- * different process, the server will generate a temporary token that
- * the organizer can use to reparent the activity through
- * {@link WindowContainerTransaction} if needed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskId The Task that the activity is reparented to.
+ * @param activityIntent The intent that the activity is original launched with.
+ * @param activityToken If the activity belongs to the same process as the organizer, this
+ * will be the actual activity token; if the activity belongs to a
+ * different process, the server will generate a temporary token that
+ * the organizer can use to reparent the activity through
+ * {@link WindowContainerTransaction} if needed.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
- int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
// If the activity belongs to the current app process, we treat it as a new activity
// launch.
final Activity activity = getActivity(activityToken);
@@ -781,14 +945,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param errorCallbackToken token set in
- * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
- * TaskFragment created.
- * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
- * transaction operation.
- * @param exception exception from the server side.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param errorCallbackToken token set in
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
+ * TaskFragment created.
+ * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
+ * transaction operation.
+ * @param exception exception from the server side.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -828,7 +993,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+ /**
+ * Called on receiving {@link #onTaskFragmentVanished} for cleanup.
+ */
@GuardedBy("mLock")
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
@@ -855,11 +1022,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Checks if the new added activity should be routed to a particular container. It can create a
* new container for the activity and a new split container if necessary.
- * @param activity the activity that is newly added to the Task.
- * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
- * We only support to split as primary for reparented activity for now.
+ *
+ * @param activity the activity that is newly added to the Task.
+ * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
+ * We only support to split as primary for reparented activity for now.
* @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or
- * in a state that the caller shouldn't handle.
+ * in a state that the caller shouldn't handle.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -892,7 +1060,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
if (!isOnReparent && taskContainer != null
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
- != container) {
+ != container) {
// Do not resolve if the launched activity is not the top-most container (excludes
// the pinned and overlay container) in the Task.
return true;
@@ -917,6 +1085,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
*/
+ @GuardedBy("mLock")
boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
@NonNull Activity activity, @Nullable TaskFragmentContainer container,
boolean isOnReparent) {
@@ -1001,7 +1170,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@VisibleForTesting
void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
- @NonNull Activity activity) {
+ @NonNull Activity activity) {
if (getContainerWithActivity(activity) != null) {
// The activity has already been put in a TaskFragment. This is likely to be done by
// the server when the activity is started.
@@ -1051,7 +1220,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
private void expandActivity(@NonNull WindowContainerTransaction wct,
- @NonNull Activity activity) {
+ @NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
@@ -1063,7 +1232,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Whether the given new launched activity is in a split with a rule matched. */
+ /**
+ * Whether the given new launched activity is in a split with a rule matched.
+ */
// Suppress GuardedBy warning because lint asks to mark this method as
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@@ -1121,7 +1292,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return getSplitRule(primaryActivity, launchedActivity) != null;
}
- /** Finds the activity below the given activity. */
+ /**
+ * Finds the activity below the given activity.
+ */
@VisibleForTesting
@Nullable
@GuardedBy("mLock")
@@ -1172,8 +1345,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- taskProperties.getTaskMetrics(),
- calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
+ taskProperties.getTaskMetrics(),
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1307,7 +1480,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* prioritize to split the new activity with it if it is not
* {@code null}.
* @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
- * is no embedding rule matched.
+ * is no embedding rule matched.
*/
@VisibleForTesting
@Nullable
@@ -1411,8 +1584,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
- return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
- null /* overlayTag */);
+ return createEmptyContainer(wct, intent, taskId,
+ new ActivityStackAttributes.Builder().build(), launchingActivity,
+ null /* overlayTag */, null /* launchOptions */);
}
/**
@@ -1425,8 +1599,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
- @NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag) {
+ @NonNull ActivityStackAttributes activityStackAttributes,
+ @Nullable Activity launchingActivity, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1443,48 +1618,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
- intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
+ launchOptions);
final IBinder taskFragmentToken = container.getTaskFragmentToken();
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
+ final Rect taskBounds = taskContainer.getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
+ getMinDimensions(intent), taskBounds);
final int windowingMode = taskContainer
- .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ .getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
sanitizedBounds, windowingMode);
- mPresenter.updateAnimationParams(wct, taskFragmentToken,
- TaskFragmentAnimationParams.DEFAULT);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
- overlayTag != null && !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
+ getMinDimensions(intent));
return container;
}
/**
- * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
- * covered by the task bounds. Otherwise, returns {@code bounds}.
- */
- @NonNull
- private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
- @NonNull Rect taskBounds) {
- if (bounds.isEmpty()) {
- // Don't need to check if the bounds follows the task bounds.
- return bounds;
- }
- if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
- // Expand the bounds if the bounds are smaller than minimum dimensions.
- return new Rect();
- }
- if (!taskBounds.contains(bounds)) {
- // Expand the bounds if the bounds exceed the task bounds.
- return new Rect();
- }
- return bounds;
- }
-
- /**
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
@@ -1507,11 +1661,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivityIntentMinDimensionsPair(primaryActivity, intent));
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
&& (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
- calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
- null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
+ null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
return splitContainer.getSecondaryContainer();
@@ -1536,29 +1690,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
// Check pending appeared activity first because there can be a delay for the server
// update.
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (container.hasPendingAppearedActivity(activityToken)) {
- return container;
- }
- }
+ TaskFragmentContainer taskFragmentContainer =
+ getContainer(container -> container.hasPendingAppearedActivity(activityToken));
+ if (taskFragmentContainer != null) {
+ return taskFragmentContainer;
}
+
// Check appeared activity if there is no such pending appeared activity.
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (container.hasAppearedActivity(activityToken)) {
- return container;
- }
- }
- }
- return null;
+ return getContainer(container -> container.hasAppearedActivity(activityToken));
}
@GuardedBy("mLock")
@@ -1570,43 +1710,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
- @NonNull Activity activityInTask, int taskId,
- @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
+ null /* launchOptions */);
}
/**
* Creates and registers a new organized container with an optional activity that will be
* re-parented to it in a WCT.
*
- * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
- * @param pendingAppearedIntent the Intent that will be started in the TaskFragment.
- * @param activityInTask activity in the same Task so that we can get the Task bounds
- * if needed.
- * @param taskId parent Task of the new TaskFragment.
- * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
- * set, the new container will be added right above it.
- * @param overlayTag The tag for the new created overlay container. It must be
- * needed if {@code isOverlay} is {@code true}. Otherwise,
- * it should be {@code null}.
+ * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
+ * @param pendingAppearedIntent the Intent that will be started in the TaskFragment.
+ * @param activityInTask activity in the same Task so that we can get the Task bounds
+ * if needed.
+ * @param taskId parent Task of the new TaskFragment.
+ * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * set, the new container will be added right above it.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
+ * @param launchOptions The launch options bundle to create a container. Must be
+ * specified for overlay container.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1615,7 +1761,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
+ launchOptions);
return container;
}
@@ -1640,7 +1787,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
}
- /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+ /**
+ * Cleanups all the dependencies when the TaskFragment is entering PIP.
+ */
@GuardedBy("mLock")
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
@@ -1799,16 +1948,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer container) {
+ @NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
+
+ if (dismissOverlayContainerIfNeeded(wct, taskContainer)) {
+ return;
+ }
+
+ if (mActivityStackAttributesCalculator == null) {
+ Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
+ + " can not be updated.");
+ return;
+ }
+
+ if (mActivityStackAttributesCalculator != null) {
+ final ActivityStackAttributesCalculatorParams params =
+ new ActivityStackAttributesCalculatorParams(
+ mPresenter.createParentContainerInfoFromTaskProperties(
+ taskContainer.getTaskProperties()),
+ container.getOverlayTag(),
+ container.getLaunchOptions());
+ final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
+ .apply(params);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
+ }
+ }
+
+ /** Dismisses the overlay container in the {@code taskContainer} if needed. */
+ @GuardedBy("mLock")
+ private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskContainer taskContainer) {
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer == null) {
+ return false;
+ }
// Dismiss the overlay container if it's the only container in the task and there's no
// direct activity in the parent task.
if (taskContainer.getTaskFragmentContainers().size() == 1
&& !taskContainer.hasDirectActivity()) {
- container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+ mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */);
+ return true;
}
-
- // TODO(b/295805054): Add the logic to update overlay container
+ return false;
}
/**
@@ -1817,11 +1999,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* are {@code null}, the {@link SplitAttributes} will be calculated with
* {@link SplitPresenter#computeSplitAttributes}.
*
- * @param splitContainer The {@link SplitContainer} to update
+ * @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
* {@link SplitPresenter#computeSplitAttributes}
- *
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@VisibleForTesting
@@ -1856,7 +2037,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- /** Whether the given split is the topmost split in the Task. */
+ /**
+ * Whether the given split is the topmost split in the Task.
+ */
private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
.getTaskContainer().getSplitContainers();
@@ -1963,7 +2146,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- /** Whether or not to allow activity in this container to launch placeholder. */
+ /**
+ * Whether or not to allow activity in this container to launch placeholder.
+ */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
final TaskFragmentContainer topContainer = container.getTaskContainer()
@@ -1997,8 +2182,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Gets the activity options for starting the placeholder activity. In case the placeholder is
* launched when the Task is in the background, we don't want to bring the Task to the front.
- * @param primaryActivity the primary activity to launch the placeholder from.
- * @param isOnCreated whether this happens during the primary activity onCreated.
+ *
+ * @param primaryActivity the primary activity to launch the placeholder from.
+ * @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -2070,7 +2256,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null || !readyToReportToClient()) {
+ updateSplitInfoCallbackIfNecessary();
+ updateActivityStackCallbackIfNecessary();
+ }
+
+ /**
+ * Notifies callbacks about changes to split states if necessary.
+ */
+ @GuardedBy("mLock")
+ private void updateSplitInfoCallbackIfNecessary() {
+ if (!readyToReportToClient() || mSplitInfoCallback == null) {
return;
}
final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
@@ -2079,7 +2274,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
mLastReportedSplitStates.clear();
mLastReportedSplitStates.addAll(currentSplitStates);
- mEmbeddingCallback.accept(currentSplitStates);
+ mSplitInfoCallback.accept(currentSplitStates);
+ }
+
+ /**
+ * Notifies callbacks about changes to {@link ActivityStack} states if necessary.
+ */
+ @GuardedBy("mLock")
+ private void updateActivityStackCallbackIfNecessary() {
+ if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) {
+ return;
+ }
+ final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable();
+ if (currentActivityStacks == null
+ || mLastReportedActivityStacks.equals(currentActivityStacks)) {
+ return;
+ }
+ mLastReportedActivityStacks.clear();
+ mLastReportedActivityStacks.addAll(currentActivityStacks);
+ // Copy the map in case a callback is removed during the for-loop.
+ final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks =
+ new ArrayMap<>(mActivityStackCallbacks);
+ for (int i = callbacks.size() - 1; i >= 0; --i) {
+ final Executor executor = callbacks.valueAt(i);
+ final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i);
+ executor.execute(() -> callback.accept(currentActivityStacks));
+ }
}
/**
@@ -2104,6 +2324,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
+ * Returns a list of currently active {@link ActivityStack activityStacks}.
+ *
+ * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+ * a stable state, or {@code null} otherwise.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private List<ActivityStack> getActivityStacksIfStable() {
+ final List<ActivityStack> activityStacks = new ArrayList<>();
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<ActivityStack> taskActivityStacks =
+ mTaskContainers.valueAt(i).getActivityStacksIfStable();
+ if (taskActivityStacks == null) {
+ return null;
+ }
+ activityStacks.addAll(taskActivityStacks);
+ }
+ return activityStacks;
+ }
+
+ /**
* Whether we can now report the split states to the client.
*/
@GuardedBy("mLock")
@@ -2173,11 +2414,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
+ return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken()));
+ }
+
+ @Nullable
+ @GuardedBy("mLock")
+ TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
.getTaskFragmentContainers();
- for (TaskFragmentContainer container : containers) {
- if (container.getTaskFragmentToken().equals(fragmentToken)) {
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (predicate.test(container)) {
return container;
}
}
@@ -2224,6 +2472,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@VisibleForTesting
+ @Nullable
+ ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ return ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ }
+
+ @VisibleForTesting
ActivityStartMonitor getActivityStartMonitor() {
return mActivityStartMonitor;
}
@@ -2236,8 +2491,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@Nullable
IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
- final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
- .getActivityClient(activity.getActivityToken());
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
return record != null ? record.mTaskFragmentToken : null;
}
@@ -2270,6 +2524,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* container. There is a case when primary containers for placeholders should be retained
* despite the rule configuration to finish primary with secondary - if they are marked as
* 'sticky' and the placeholder was finished when fully overlapping the primary container.
+ *
* @return {@code true} if the associated container should be retained (and not be finished).
*/
// Suppress GuardedBy warning because lint ask to mark this method as
@@ -2345,55 +2600,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@Nullable
TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull WindowContainerTransaction wct,
- @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
@NonNull Intent intent, @NonNull Activity launchActivity) {
- final int taskId = overlayCreateParams.getTaskId();
- if (taskId != launchTaskId) {
- // The task ID doesn't match the launch activity's. Cannot determine the host task
- // to launch the overlay.
- throw new IllegalArgumentException("The task ID of "
- + "OverlayCreateParams#launchingActivity must match the task ID of "
- + "the activity to #startActivity with the activity options that takes "
- + "OverlayCreateParams.");
- }
final List<TaskFragmentContainer> overlayContainers =
getAllOverlayTaskFragmentContainers();
- final String overlayTag = overlayCreateParams.getTag();
+ final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
- final Rect bounds = overlayCreateParams.getBounds();
- final Size minDimensions = getMinDimensions(intent);
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
- minDimensions);
+ final ActivityStackAttributesCalculatorParams params =
+ new ActivityStackAttributesCalculatorParams(
+ mPresenter.createParentContainerInfoFromTaskProperties(
+ mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+ // Fallback to expand the bounds if there's no activityStackAttributes calculator.
+ final ActivityStackAttributes attrs;
+ if (mActivityStackAttributesCalculator != null) {
+ attrs = mActivityStackAttributesCalculator.apply(params);
+ } else {
+ attrs = new ActivityStackAttributes.Builder().build();
+ Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
+ + "container as expected.");
+ }
+
+ final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
if (!overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
// If there's an overlay container with different tag shown in the same
// task, dismiss the existing overlay container.
- overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
- wct, SplitController.this);
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId != overlayContainer.getTaskId()) {
// If there's an overlay container with same tag in a different task,
// dismiss the overlay container since the tag must be unique per process.
- overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
- wct, SplitController.this);
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
- // If there's an overlay container with the same tag and task ID, we treat
- // the OverlayCreateParams as the update to the container.
- final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
- .getTaskMetrics().getBounds();
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
- mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
- !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
// We can just return the updated overlay container and don't need to
// check other condition since we only have one OverlayCreateParams, and
// if the tag and task are matched, it's impossible to match another task
@@ -2402,8 +2651,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
}
- return createEmptyContainer(wct, intent, taskId,
- (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ // Launch the overlay container to the task with taskId.
+ return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
+ options);
}
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2504,7 +2754,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Executor that posts on the main application thread. */
+ /**
+ * Executor that posts on the main application thread.
+ */
private static class MainThreadExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -2530,8 +2782,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// TODO(b/232042367): Consolidate the activity create handling so that we can handle
// cross-process the same as normal.
+ final Bundle bundle = options.getBundle(KEY_ACTIVITY_STACK_TOKEN);
+ if (bundle != null) {
+ final IBinder activityStackToken = ActivityStack.Token.readFromBundle(bundle)
+ .getRawToken();
+ // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity
+ // into the taskFragment associated with the token.
+ options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken);
+ }
+
// Early return if the launching taskfragment is already been set.
- if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
+ // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to
+ // bundle. This is still needed to support #setLaunchingActivityStack.
+ if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
synchronized (mLock) {
mCurrentIntent = intent;
}
@@ -2568,12 +2831,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- final OverlayCreateParams overlayCreateParams =
- OverlayCreateParams.fromBundle(options);
+ final String overlayTag = options.getString(KEY_OVERLAY_TAG);
if (Flags.activityEmbeddingOverlayPresentationFlag()
- && overlayCreateParams != null) {
+ && overlayTag != null) {
launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
- overlayCreateParams, taskId, intent, launchingActivity);
+ options, intent, launchingActivity);
} else {
launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
launchingActivity);
@@ -2589,7 +2851,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
// TODO(b/229680885): skip override launching TaskFragment token by split-rule
- options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
} else {
@@ -2607,8 +2869,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (mCurrentIntent != null && result != START_SUCCESS) {
// Clear the pending appeared intent if the activity was not started
// successfully.
- final IBinder token = bOptions.getBinder(
- ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+ final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
if (token != null) {
final TaskFragmentContainer container = getContainer(token);
if (container != null) {
@@ -2627,11 +2888,112 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
synchronized (mLock) {
+ if (Flags.activityWindowInfoFlag()) {
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
+ }
return mPresenter.isActivityEmbedded(activity.getActivityToken());
}
}
+ @Override
+ public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
+ @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ ClientTransactionListenerController.getInstance()
+ .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
+ }
+ }
+
+ @Override
+ public void clearEmbeddedActivityWindowInfoCallback() {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ mEmbeddedActivityWindowInfoCallback = null;
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
+ return mActivityWindowInfoListener;
+ }
+
+ @Nullable
+ @Override
+ public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return null;
+ }
+ synchronized (mLock) {
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null
+ ? translateActivityWindowInfo(activity, activityWindowInfo)
+ : null;
+ }
+ }
+
+ @VisibleForTesting
+ void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
+ final Consumer<EmbeddedActivityWindowInfo> callback =
+ mEmbeddedActivityWindowInfoCallback.second;
+
+ final Activity activity = getActivity(activityToken);
+ if (activity == null) {
+ return;
+ }
+ final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
+ activity, activityWindowInfo);
+
+ executor.execute(() -> callback.accept(info));
+ }
+ }
+
+ @Nullable
+ private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ return null;
+ }
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
+ return record != null ? record.getActivityWindowInfo() : null;
+ }
+
+ @NonNull
+ private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
+ @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
+ final boolean isEmbedded = activityWindowInfo.isEmbedded();
+ final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
+ .windowConfiguration.getBounds());
+ final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
+ final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
+ return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+ activityStackBounds);
+ }
+
/**
* If the two rules have the same presentation, and the calculated {@link SplitAttributes}
* matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
@@ -2653,7 +3015,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& calculatedSplitAttributes.equals(containerSplitAttributes);
}
- /** Whether the two rules have the same presentation. */
+ /**
+ * Whether the two rules have the same presentation.
+ */
@VisibleForTesting
static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
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 b5c32bbe78fa..b53b9c519cb6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -18,6 +18,8 @@ package androidx.window.extensions.embedding;
import static android.content.pm.PackageManager.MATCH_ALL;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
+
import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration;
@@ -54,6 +56,7 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.List;
@@ -187,7 +190,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(secondaryRelBounds);
+ .getWindowingModeForTaskFragment(secondaryRelBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode);
updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
@@ -259,7 +262,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (container == null || container == containerToAvoid) {
container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(relBounds);
+ .getWindowingModeForTaskFragment(relBounds);
final IBinder reparentActivityToken = activity.getActivityToken();
createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
relBounds, windowingMode, reparentActivityToken);
@@ -268,7 +271,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} else {
resizeTaskFragmentIfRegistered(wct, container, relBounds);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(relBounds);
+ .getWindowingModeForTaskFragment(relBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
@@ -310,7 +313,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Pass in the primary container to make sure it is added right above the primary.
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
- final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
primaryRelBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
@@ -347,6 +350,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
&& secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
&& !secondaryRelBounds.isEmpty();
+ // TODO(b/243518738): remove usages of XXXIfRegistered.
// If the task fragments are not registered yet, the positions will be updated after they
// are created again.
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds);
@@ -357,7 +361,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
primaryRelBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
@@ -381,6 +385,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
+ // Sets the dim area when the two TaskFragments are adjacent.
+ final boolean dimOnTask = !isStacked
+ && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
+ && Flags.fullscreenDimFlag();
+ setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask);
+ setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask);
+
// Setting isolated navigation and clear non-sticky pinned container if needed.
final SplitPinRule splitPinRule =
splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null;
@@ -398,13 +409,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
*/
void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer taskFragmentContainer,
+ @NonNull TaskFragmentContainer container,
boolean isolatedNavigationEnabled) {
- if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
return;
}
- taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
- setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ container.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, container.getTaskFragmentToken(),
isolatedNavigationEnabled);
}
@@ -413,7 +424,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* creation has not been reported from the server yet.
*/
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
- private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ @VisibleForTesting
+ void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@Nullable Rect relBounds) {
if (container.getInfo() == null) {
@@ -422,7 +434,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
}
- private void updateTaskFragmentWindowingModeIfRegistered(
+ @VisibleForTesting
+ void updateTaskFragmentWindowingModeIfRegistered(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@WindowingMode int windowingMode) {
@@ -566,6 +579,72 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.setCompanionTaskFragment(wct, primary, secondary);
}
+ void applyActivityStackAttributes(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @NonNull ActivityStackAttributes attributes,
+ @Nullable Size minDimensions) {
+ final Rect taskBounds = container.getTaskContainer().getBounds();
+ final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
+ taskBounds);
+ final boolean isFillParent = relativeBounds.isEmpty();
+ final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ final boolean dimOnTask = !isFillParent
+ && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
+ && Flags.fullscreenDimFlag();
+ final IBinder fragmentToken = container.getTaskFragmentToken();
+
+ // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
+ // and WCT#setWindowingMode to take fragmentToken.
+ resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
+ int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
+ relativeBounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
+ // Always use default animation for standalone ActivityStack.
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
+ setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
+ }
+
+ @Override
+ void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, boolean dimOnTask) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("setTaskFragmentDimOnTask on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastDimOnTask() == dimOnTask) {
+ return;
+ }
+
+ container.setLastDimOnTask(dimOnTask);
+ super.setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
@@ -854,8 +933,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new SplitAttributes.Builder()
.setSplitType(splitTypeToUpdate)
.setLayoutDirection(splitAttributes.getLayoutDirection())
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .setAnimationBackground(splitAttributes.getAnimationBackground())
.build();
}
@@ -1084,4 +1162,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
return getTaskProperties(activity).getTaskMetrics();
}
+
+ @NonNull
+ ParentContainerInfo createParentContainerInfoFromTaskProperties(
+ @NonNull TaskProperties taskProperties) {
+ final Configuration configuration = taskProperties.getConfiguration();
+ final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
+ .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+ configuration.windowConfiguration);
+ return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
+ windowLayoutInfo);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 028e75fe010f..73109e266905 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -126,6 +126,11 @@ class TaskContainer {
}
@NonNull
+ Rect getBounds() {
+ return mConfiguration.windowConfiguration.getBounds();
+ }
+
+ @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
@@ -138,6 +143,21 @@ class TaskContainer {
}
/**
+ * Returns {@code true} if the container should be updated with {@code info}.
+ */
+ boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
+ final Configuration configuration = info.getConfiguration();
+
+ return info.isVisible()
+ // No need to update presentation in PIP until the Task exit PIP.
+ && !isInPictureInPicture(configuration)
+ // If the task properties equals regardless of starting position, don't need to
+ // update the container.
+ && (mConfiguration.diffPublicOnly(configuration) != 0
+ || mDisplayId != info.getDisplayId());
+ }
+
+ /**
* Returns the windowing mode for the TaskFragments below this Task, which should be split with
* other TaskFragments.
*
@@ -145,7 +165,7 @@ class TaskContainer {
* the pair of TaskFragments are stacked due to the limited space.
*/
@WindowingMode
- int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
+ int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) {
// Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
// will be set to UNDEFINED which will then inherit the Task windowing mode.
if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) {
@@ -161,7 +181,11 @@ class TaskContainer {
}
boolean isInPictureInPicture() {
- return getWindowingMode() == WINDOWING_MODE_PINNED;
+ return isInPictureInPicture(mConfiguration);
+ }
+
+ private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
+ return configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
boolean isInMultiWindow() {
@@ -443,6 +467,26 @@ class TaskContainer {
return splitStates;
}
+ // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable.
+ /**
+ * Returns a list of currently active {@link ActivityStack activityStacks}.
+ *
+ * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+ * a stable state, or {@code null} otherwise.
+ */
+ @Nullable
+ List<ActivityStack> getActivityStacksIfStable() {
+ final List<ActivityStack> activityStacks = new ArrayList<>();
+ for (TaskFragmentContainer container : mContainers) {
+ final ActivityStack activityStack = container.toActivityStackIfStable();
+ if (activityStack == null) {
+ return null;
+ }
+ activityStacks.add(activityStack);
+ }
+ return activityStacks;
+ }
+
/** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
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 3e7f99b96421..a6bf99d4add5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -24,6 +24,7 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.util.Size;
import android.window.TaskFragmentAnimationParams;
@@ -105,6 +106,13 @@ class TaskFragmentContainer {
@Nullable
private final String mOverlayTag;
+ /**
+ * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
+ * for {@link #isOverlay()} container.
+ */
+ @NonNull
+ private final Bundle mLaunchOptions = new Bundle();
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -164,8 +172,13 @@ class TaskFragmentContainer {
private boolean mIsIsolatedNavigationEnabled;
/**
+ * Whether to apply dimming on the parent Task that was requested last.
+ */
+ private boolean mLastDimOnTask;
+
+ /**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
- * TaskFragmentContainer, String)
+ * TaskFragmentContainer, String, Bundle)
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent,
@@ -173,7 +186,8 @@ class TaskFragmentContainer {
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
- controller, pairedPrimaryContainer, null /* overlayTag */);
+ controller, pairedPrimaryContainer, null /* overlayTag */,
+ null /* launchOptions */);
}
/**
@@ -181,11 +195,14 @@ class TaskFragmentContainer {
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
* @param overlayTag Sets to indicate this taskFragment is an overlay container
+ * @param launchOptions The launch options to create this container. Must not be
+ * {@code null} for an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -195,6 +212,12 @@ class TaskFragmentContainer {
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
mOverlayTag = overlayTag;
+ if (overlayTag != null) {
+ Objects.requireNonNull(launchOptions);
+ }
+ if (launchOptions != null) {
+ mLaunchOptions.putAll(launchOptions);
+ }
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
@@ -344,7 +367,8 @@ class TaskFragmentContainer {
if (activities == null) {
return null;
}
- return new ActivityStack(activities, isEmpty(), mToken);
+ return new ActivityStack(activities, isEmpty(),
+ ActivityStack.Token.createFromBinder(mToken), mOverlayTag);
}
/** Adds the activity that will be reparented to this container. */
@@ -593,6 +617,9 @@ class TaskFragmentContainer {
* Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
@@ -818,6 +845,16 @@ class TaskFragmentContainer {
mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
}
+ /** Sets whether to apply dim on the parent Task. */
+ void setLastDimOnTask(boolean lastDimOnTask) {
+ mLastDimOnTask = lastDimOnTask;
+ }
+
+ /** Returns whether to apply dim on the parent Task. */
+ boolean isLastDimOnTask() {
+ return mLastDimOnTask;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -901,14 +938,25 @@ class TaskFragmentContainer {
}
/**
- * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
- * taskFragment container is not an overlay container.
+ * Returns the tag specified in launch options. {@code null} if this taskFragment container is
+ * not an overlay container.
*/
@Nullable
String getOverlayTag() {
return mOverlayTag;
}
+ /**
+ * Returns the options that was used to launch this {@link TaskFragmentContainer}.
+ * {@link Bundle#isEmpty()} means there's no launch option for this container.
+ * <p>
+ * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object.
+ */
+ @NonNull
+ Bundle getLaunchOptions() {
+ return mLaunchOptions;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
index 396956e56bb5..6624c703f027 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -77,9 +77,11 @@ class TransactionManager {
@NonNull
TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
if (mCurrentTransaction != null) {
+ final TransactionRecord lastTransaction = mCurrentTransaction;
mCurrentTransaction = null;
throw new IllegalStateException(
- "The previous transaction has not been applied or aborted,");
+ "The previous transaction:" + lastTransaction + " has not been applied or "
+ + "aborted.");
}
mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
return mCurrentTransaction;
@@ -199,5 +201,15 @@ class TransactionManager {
? mOriginType
: TASK_FRAGMENT_TRANSIT_CHANGE;
}
+
+ @Override
+ @NonNull
+ public String toString() {
+ return TransactionRecord.class.getSimpleName() + "{"
+ + "token=" + mTaskFragmentTransactionToken
+ + ", type=" + getTransactionTransitionType()
+ + ", transaction=" + mTransaction
+ + "}";
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
new file mode 100644
index 000000000000..a0f481a911ad
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.layout;
+
+import androidx.window.common.CommonFoldingFeature;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Util functions for working with {@link androidx.window.extensions.layout.DisplayFoldFeature}.
+ */
+public class DisplayFoldFeatureUtil {
+
+ private DisplayFoldFeatureUtil() {}
+
+ private static DisplayFoldFeature create(CommonFoldingFeature foldingFeature,
+ boolean isHalfOpenedSupported) {
+ final int foldType;
+ if (foldingFeature.getType() == CommonFoldingFeature.COMMON_TYPE_HINGE) {
+ foldType = DisplayFoldFeature.TYPE_HINGE;
+ } else {
+ foldType = DisplayFoldFeature.TYPE_SCREEN_FOLD_IN;
+ }
+ DisplayFoldFeature.Builder featureBuilder = new DisplayFoldFeature.Builder(foldType);
+
+ if (isHalfOpenedSupported) {
+ featureBuilder.addProperty(DisplayFoldFeature.FOLD_PROPERTY_SUPPORTS_HALF_OPENED);
+ }
+ return featureBuilder.build();
+ }
+
+ /**
+ * Returns the list of supported {@link DisplayFeature} calculated from the
+ * {@link DeviceStateManagerFoldingFeatureProducer}.
+ */
+ public static List<DisplayFoldFeature> extractDisplayFoldFeatures(
+ DeviceStateManagerFoldingFeatureProducer producer) {
+ List<DisplayFoldFeature> foldFeatures = new ArrayList<>();
+ List<CommonFoldingFeature> folds = producer.getFoldsWithUnknownState();
+
+ final boolean isHalfOpenedSupported = producer.isHalfOpenedSupported();
+ for (CommonFoldingFeature fold : folds) {
+ foldFeatures.add(DisplayFoldFeatureUtil.create(fold, isHalfOpenedSupported));
+ }
+ return foldFeatures;
+ }
+}
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 9b84a48cdbda..4fd11c495529 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -45,7 +45,6 @@ import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.core.util.function.Consumer;
-import androidx.window.util.DataProducer;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,10 +55,6 @@ import java.util.Set;
/**
* Reference implementation of androidx.window.extensions.layout OEM interface for use with
* WindowManager Jetpack.
- *
- * NOTE: This version is a work in progress and under active development. It MUST NOT be used in
- * production builds since the interface can still change before reaching stable version.
- * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = WindowLayoutComponentImpl.class.getSimpleName();
@@ -71,7 +66,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
new ArrayMap<>();
@GuardedBy("mLock")
- private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
+ private final DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
@GuardedBy("mLock")
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
@@ -87,12 +82,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final RawConfigurationChangedListener mRawConfigurationChangedListener =
new RawConfigurationChangedListener();
+ private final SupportedWindowFeatures mSupportedWindowFeatures;
+
public WindowLayoutComponentImpl(@NonNull Context context,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+ final List<DisplayFoldFeature> displayFoldFeatures =
+ DisplayFoldFeatureUtil.extractDisplayFoldFeatures(mFoldingFeatureProducer);
+ mSupportedWindowFeatures = new SupportedWindowFeatures.Builder(displayFoldFeatures).build();
}
/**
@@ -283,6 +283,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
+ /**
+ * Returns the {@link SupportedWindowFeatures} for the device. This list does not change over
+ * time.
+ */
+ @NonNull
+ public SupportedWindowFeatures getSupportedWindowFeatures() {
+ return mSupportedWindowFeatures;
+ }
+
/** @see #getWindowLayoutInfo(Context, List) */
private WindowLayoutInfo getWindowLayoutInfo(int displayId,
@NonNull WindowConfiguration windowConfiguration,
@@ -356,7 +365,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
if (featureRect.left == 0
&& featureRect.width() != windowConfiguration.getBounds().width()) {
- Log.wtf(TAG, "Horizontal FoldingFeature must have full width."
+ Log.w(TAG, "Horizontal FoldingFeature must have full width."
+ " BaseFeatureRect: " + baseFeature.getRect()
+ ", FeatureRect: " + featureRect
+ ", WindowConfiguration: " + windowConfiguration);
@@ -364,7 +373,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
if (featureRect.top == 0
&& featureRect.height() != windowConfiguration.getBounds().height()) {
- Log.wtf(TAG, "Vertical FoldingFeature must have full height."
+ Log.w(TAG, "Vertical FoldingFeature must have full height."
+ " BaseFeatureRect: " + baseFeature.getRect()
+ ", FeatureRect: " + featureRect
+ ", WindowConfiguration: " + windowConfiguration);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index a836e05b2d66..56c3bce87d6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -17,6 +17,7 @@
package androidx.window.sidecar;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
@@ -25,6 +26,7 @@ import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -49,10 +51,11 @@ class SampleSidecarImpl extends StubSidecar {
SampleSidecarImpl(Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
- BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+ RawFoldingFeatureProducer settingsFeatureProducer = new RawFoldingFeatureProducer(context);
BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
new DeviceStateManagerFoldingFeatureProducer(context,
- settingsFeatureProducer);
+ settingsFeatureProducer,
+ context.getSystemService(DeviceStateManager.class));
foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 60beb0b7f0a4..f471af052bf2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -25,6 +25,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.AnimationBackground;
import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
@@ -70,7 +71,7 @@ public class WindowExtensionsTest {
.isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
assertThat(splitAttributes.getSplitType())
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
- // TODO(b/263047900): Update extensions API.
- // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ assertThat(splitAttributes.getAnimationBackground())
+ .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4c2433fab2f8..28fbadbebe7f 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,16 +16,17 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
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.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +39,13 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -57,6 +58,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -98,9 +101,6 @@ public class OverlayPresentationTest {
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
- private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
- new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
-
private SplitController.ActivityStartMonitor mMonitor;
private Intent mIntent;
@@ -165,37 +165,15 @@ public class OverlayPresentationTest {
}
@Test
- public void testOverlayCreateParamsFromBundle() {
- assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
-
- assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
- .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
- }
-
- @Test
public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
- mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+ final Bundle optionsBundle = ActivityOptions.makeBasic().toBundle();
+ optionsBundle.putString(KEY_OVERLAY_TAG, "test");
+ mMonitor.onStartActivity(mActivity, mIntent, optionsBundle);
verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
- anyInt(), any(), any());
- }
-
- @NonNull
- private static Bundle createOverlayCreateParamsTestBundle() {
- final Bundle bundle = new Bundle();
-
- final Bundle paramsBundle = new Bundle();
- paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
- TEST_OVERLAY_CREATE_PARAMS.getTaskId());
- paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
- paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
- TEST_OVERLAY_CREATE_PARAMS.getBounds());
-
- bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
-
- return bundle;
+ any(), any());
}
@Test
@@ -221,19 +199,11 @@ public class OverlayPresentationTest {
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
- assertThrows("The method must return null due to task mismatch between"
- + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
- () -> createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
- }
-
- @Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test3");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched to the same task")
@@ -245,9 +215,9 @@ public class OverlayPresentationTest {
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
- TASK_ID + 2);
+ doReturn(TASK_ID + 2).when(mActivity).getTaskId();
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched with the same tag as an existing overlay container in a different "
@@ -261,9 +231,10 @@ public class OverlayPresentationTest {
createExistingOverlayContainers();
final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test1", bounds),
- TASK_ID);
+ "test1");
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -279,9 +250,8 @@ public class OverlayPresentationTest {
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
- TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test2");
// OverlayContainer1 is dismissed since new container is launched in the same task with
// different tag. OverlayContainer2 is dismissed since new container is launched with the
@@ -300,70 +270,37 @@ public class OverlayPresentationTest {
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ public void testSanitizeBounds_smallerThanMinDimens_expandOverlay() {
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
+ final Rect bounds = new Rect(0, 0, 100, 100);
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
+ SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ TASK_BOUNDS);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
- "test", bounds);
-
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- paramsOutsideTaskBounds, TASK_ID);
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
+ SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
}
@Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ setupTaskFragmentInfo(overlayContainer, mActivity);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
- assertThat(overlayContainer
- .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
- assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo("test");
}
@Test
@@ -416,12 +353,14 @@ public class OverlayPresentationTest {
@Test
public void testGetTopNonFinishingActivityWithOverlay() {
- createTestOverlayContainer(TASK_ID, "test1");
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1");
+
final Activity activity = createMockActivity();
final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
final TaskContainer task = container.getTaskContainer();
- assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+ assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */))
+ .isEqualTo(overlayContainer.getTopNonFinishingActivity());
assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
}
@@ -453,15 +392,197 @@ public class OverlayPresentationTest {
.that(taskContainer.getTaskFragmentContainers()).isEmpty();
}
+ @Test
+ public void testUpdateActivityStackAttributes_nullParams_throwException() {
+ assertThrows(NullPointerException.class, () ->
+ mSplitController.updateActivityStackAttributes(null,
+ new ActivityStackAttributes.Builder().build()));
+
+ assertThrows(NullPointerException.class, () ->
+ mSplitController.updateActivityStackAttributes(
+ ActivityStack.Token.createFromBinder(new Binder()), null));
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() {
+ final TaskFragmentContainer container = mSplitController.newContainer(mActivity,
+ mActivity.getTaskId());
+ mSplitController.updateActivityStackAttributes(
+ ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()),
+ new ActivityStackAttributes.Builder().build());
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ mSplitController.updateActivityStackAttributes(
+ ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()),
+ new ActivityStackAttributes.Builder().build());
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+ doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any(), any());
+ final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
+ final IBinder token = container.getTaskFragmentToken();
+
+ mSplitController.updateActivityStackAttributes(ActivityStack.Token.createFromBinder(token),
+ attrs);
+
+ verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
+ any());
+ }
+
+ @Test
+ public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+ assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
+ spyOn(taskContainer);
+ final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+ new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+ true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+ parentInfo.getConfiguration().windowConfiguration.getBounds().offset(10, 10);
+
+ mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+ // The parent info must be applied to the task container
+ verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+ verify(mSplitController, never()).updateContainer(any(), any());
+
+ assertWithMessage("The overlay container must still be dismissed even if "
+ + "#updateContainer is not called")
+ .that(taskContainer.getOverlayContainer()).isNull();
+ }
+
+ @Test
+ public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissOverlayContainer() {
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+ assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
+ spyOn(taskContainer);
+ final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+ new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+ false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+
+ mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+ // The parent info must be applied to the task container
+ verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+ verify(mSplitController, never()).updateContainer(any(), any());
+
+ assertWithMessage("The overlay container must still be dismissed even if "
+ + "#updateContainer is not called")
+ .that(taskContainer.getOverlayContainer()).isNull();
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForExpandedContainer() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForExpandedOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer_exceedsMinDimensions() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final Rect relativeBounds = new Rect(0, 0, 200, 200);
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(relativeBounds)
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ new Size(relativeBounds.width() + 1, relativeBounds.height()));
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ new Rect());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@Nullable
- private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull OverlayCreateParams params, int taskId) {
- return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
- taskId, mIntent, mActivity);
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+ launchOptions, mIntent, mActivity);
}
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -475,10 +596,11 @@ public class OverlayPresentationTest {
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ Activity activity = createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
- null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
- null /* pairedPrimaryContainer */, tag);
- setupTaskFragmentInfo(overlayContainer, mActivity);
+ null /* pendingAppearedActivity */, mIntent, activity, taskId,
+ null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
+ setupTaskFragmentInfo(overlayContainer, activity);
return overlayContainer;
}
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 8c274a26177d..bdeeb7304b12 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
@@ -72,6 +72,8 @@ import static org.mockito.Mockito.times;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -110,6 +117,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -127,6 +136,9 @@ public class SplitControllerTest {
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
new ComponentName("test", "placeholder"));
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
private Activity mActivity;
@Mock
private Resources mActivityResources;
@@ -138,6 +150,13 @@ public class SplitControllerTest {
private Handler mHandler;
@Mock
private WindowLayoutComponentImpl mWindowLayoutComponent;
+ @Mock
+ private ActivityWindowInfo mActivityWindowInfo;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
+ mEmbeddedActivityWindowInfoCallback;
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
@@ -354,7 +373,7 @@ public class SplitControllerTest {
bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
container.getTaskFragmentToken());
monitor.mCurrentIntent = intent;
- doReturn(container).when(mSplitController).getContainer(any());
+ doReturn(container).when(mSplitController).getContainer(any(IBinder.class));
monitor.onStartActivityResult(START_CANCELED, bundle);
assertNull(container.getPendingAppearedIntent());
@@ -590,7 +609,7 @@ public class SplitControllerTest {
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
}
@Test
@@ -753,7 +772,7 @@ public class SplitControllerTest {
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -796,7 +815,7 @@ public class SplitControllerTest {
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -1437,7 +1456,7 @@ public class SplitControllerTest {
@Test
public void testUpdateSplitAttributes_nullParams_throwException() {
assertThrows(NullPointerException.class,
- () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES));
+ () -> mSplitController.updateSplitAttributes((IBinder) null, SPLIT_ATTRIBUTES));
final SplitContainer splitContainer = mock(SplitContainer.class);
final IBinder token = new Binder();
@@ -1529,6 +1548,73 @@ public class SplitControllerTest {
.getTopNonFinishingActivity(), secondaryActivity);
}
+ @Test
+ public void testIsActivityEmbedded() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ assertFalse(mSplitController.isActivityEmbedded(mActivity));
+
+ doReturn(true).when(mActivityWindowInfo).isEmbedded();
+
+ assertTrue(mSplitController.isActivityEmbedded(mActivity));
+ }
+
+ @Test
+ public void testGetEmbeddedActivityWindowInfo() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final boolean isEmbedded = true;
+ final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
+ .getBounds();
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
+ doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
+ doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
+ doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
+
+ final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
+ isEmbedded, activityBounds, taskBounds, activityStackBounds);
+ assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
+ }
+
+ @Test
+ public void testSetEmbeddedActivityWindowInfoCallback() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final ClientTransactionListenerController controller = ClientTransactionListenerController
+ .getInstance();
+ spyOn(controller);
+ doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
+ doReturn(mActivityWindowInfoListener).when(mSplitController)
+ .getActivityWindowInfoListener();
+ final Executor executor = Runnable::run;
+
+ // Register to ClientTransactionListenerController
+ mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
+ mEmbeddedActivityWindowInfoCallback);
+
+ verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+
+ // Test onActivityWindowInfoChanged triggered.
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback).accept(any());
+
+ // Unregister to ClientTransactionListenerController
+ mSplitController.clearEmbeddedActivityWindowInfoCallback();
+
+ verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
+ clearInvocations(mEmbeddedActivityWindowInfoCallback);
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@ public class SplitControllerTest {
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
+ final ActivityThread.ActivityClientRecord activityClientRecord =
+ mock(ActivityThread.ActivityClientRecord.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
return activity;
}
@@ -1642,7 +1732,7 @@ public class SplitControllerTest {
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
- .getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
+ .getWindowingModeForTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */));
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 6981d9d7ebb8..941b4e1c3e41 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
@@ -235,6 +235,19 @@ public class SplitPresenterTest {
}
@Test
+ public void testSetTaskFragmentDimOnTask() {
+ final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true);
+ verify(mTransaction).addTaskFragmentOperation(eq(container.getTaskFragmentToken()), any());
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true);
+ verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
+ }
+
+ @Test
public void testUpdateAnimationParams() {
final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 7b77235f66f7..a5995a3027ac 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
@@ -75,7 +75,7 @@ public class TaskContainerTest {
final Configuration configuration = new Configuration();
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -83,7 +83,7 @@ public class TaskContainerTest {
null /* decorSurface */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -91,12 +91,12 @@ public class TaskContainerTest {
null /* decorSurface */));
assertEquals(WINDOWING_MODE_FREEFORM,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
// Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then
// inherit the Task windowing mode
assertEquals(WINDOWING_MODE_UNDEFINED,
- taskContainer.getWindowingModeForSplitTaskFragment(new Rect()));
+ taskContainer.getWindowingModeForTaskFragment(new Rect()));
}
@Test
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 5ad144d50b87..8829d1b9e0e1 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_multitasking_windowing",
}
// Begin ProtoLog
@@ -39,6 +40,7 @@ filegroup {
}
// Sources that have no dependencies that can be used directly downstream of this library
+// TODO(b/322791067): move these sources to WindowManager-Shell-shared
filegroup {
name: "wm_shell_util-sources",
srcs: [
@@ -80,16 +82,18 @@ filegroup {
genrule {
name: "wm_shell_protolog_src",
srcs: [
+ ":protolog-impl",
":wm_shell_protolog-groups",
":wm_shell-sources",
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.common.ProtoLog " +
- "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " +
- "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
+ "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
+ "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
+ "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
"--output-srcjar $(out) " +
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.srcjar"],
@@ -106,12 +110,30 @@ genrule {
"--protolog-class com.android.internal.protolog.common.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
- "--viewer-conf $(out) " +
+ "--viewer-config-type json " +
+ "--viewer-config $(out) " +
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.json"],
}
genrule {
+ name: "gen-wmshell.protolog.pb",
+ srcs: [
+ ":wm_shell_protolog-groups",
+ ":wm_shell-sources",
+ ],
+ tools: ["protologtool"],
+ cmd: "$(location protologtool) generate-viewer-config " +
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
+ "--loggroups-jar $(location :wm_shell_protolog-groups) " +
+ "--viewer-config-type proto " +
+ "--viewer-config $(out) " +
+ "$(locations :wm_shell-sources)",
+ out: ["wmshell.protolog.pb"],
+}
+
+genrule {
name: "protolog.json.gz",
srcs: [":generate-wm_shell_protolog.json"],
out: ["wmshell.protolog.json.gz"],
@@ -125,6 +147,13 @@ prebuilt_etc {
filename_from_src: true,
}
+prebuilt_etc {
+ name: "wmshell.protolog.pb",
+ system_ext_specific: true,
+ src: ":gen-wmshell.protolog.pb",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -137,6 +166,12 @@ java_library {
},
}
+java_library {
+ name: "WindowManager-Shell-shared",
+
+ srcs: ["shared/**/*.java"],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
@@ -162,6 +197,8 @@ android_library {
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
+ "perfetto_trace_java_protos",
"dagger2",
"jsr330",
],
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index e346b51a4f19..0c4fd140780e 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -2,3 +2,4 @@ xutan@google.com
# Give submodule owners in shell resource approval
per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com
+per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/libs/WindowManager/Shell/aconfig/OWNERS b/libs/WindowManager/Shell/aconfig/OWNERS
new file mode 100644
index 000000000000..9eba0f2dea7b
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/OWNERS
@@ -0,0 +1,3 @@
+# Owners for flag changes
+madym@google.com
+hwwang@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4511f3b91c5c..b61dda4c4e53 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
package: "com.android.wm.shell"
flag {
- name: "example_flag"
- namespace: "multitasking"
- description: "An Example Flag"
- bug: "300136750"
-}
-
-flag {
name: "enable_app_pairs"
namespace: "multitasking"
description: "Enables the ability to create and save app pairs to the Home screen"
@@ -15,14 +8,6 @@ flag {
}
flag {
- name: "enable_desktop_windowing"
- namespace: "multitasking"
- description: "Enables desktop windowing"
- bug: "304778354"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_split_contextual"
namespace: "multitasking"
description: "Enables invoking split contextually"
@@ -37,13 +22,6 @@ flag {
}
flag {
- name: "enable_pip_ui_state_on_entering"
- namespace: "multitasking"
- description: "Enables PiP UI state callback on entering"
- bug: "303718131"
-}
-
-flag {
name: "enable_pip2_implementation"
namespace: "multitasking"
description: "Enables the new implementation of PiP (PiP2)"
@@ -57,3 +35,31 @@ flag {
description: "Enables left/right split in portrait"
bug: "291018646"
}
+
+flag {
+ name: "enable_new_bubble_animations"
+ namespace: "multitasking"
+ description: "Enables new animations for expand and collapse for bubbles"
+ bug: "311450609"
+}
+
+flag {
+ name: "enable_pip_umo_experience"
+ namespace: "multitasking"
+ description: "Enables new UMO experience for PiP menu"
+ bug: "307998712"
+}
+
+flag {
+ name: "enable_bubble_bar"
+ namespace: "multitasking"
+ description: "Enables the new bubble bar UI for tablets"
+ bug: "286246694"
+}
+
+flag {
+ name: "enable_bubbles_long_press_nav_handle"
+ namespace: "multitasking"
+ description: "Enables long-press action for nav handle when a bubble is expanded"
+ bug: "324910035"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
new file mode 100644
index 000000000000..1686d0d54dc4
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_multitasking_windowing",
+}
+
+android_app {
+ name: "WindowManagerShellRobolectric",
+ platform_apis: true,
+ static_libs: [
+ "WindowManager-Shell",
+ ],
+ manifest: "AndroidManifestRobolectric.xml",
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "WMShellRobolectricTests",
+ instrumentation_for: "WindowManagerShellRobolectric",
+ upstream: true,
+ java_resource_dirs: [
+ "robolectric/config",
+ ],
+ srcs: [
+ "src/**/*.kt",
+ ],
+ // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
+ exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
+ static_libs: [
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-robolectric-prebuilt",
+ "mockito-kotlin2",
+ "truth",
+ ],
+ auto_gen_config: true,
+}
+
+android_test {
+ name: "WMShellMultivalentTestsOnDevice",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "WindowManager-Shell",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "frameworks-base-testutils",
+ "mockito-kotlin2",
+ "mockito-target-extended-minus-junit4",
+ "truth",
+ "platform-test-annotations",
+ "platform-test-rules",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ optimize: {
+ enabled: false,
+ },
+ test_suites: ["device-tests"],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.wm.shell",
+ ],
+ manifest: "AndroidManifest.xml",
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
new file mode 100644
index 000000000000..f8f8338e5f04
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalenttests">
+
+ <application android:debuggable="true" android:supportsRtl="true" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Multivalent tests for WindowManager-Shell"
+ android:targetPackage="com.android.wm.shell.multivalenttests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
new file mode 100644
index 000000000000..ffcd7d46fbae
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalenttests">
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
new file mode 100644
index 000000000000..36fe8ec3370d
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="WMShellMultivalentTestsOnDevice.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="WMShellMultivalentTestsOnDevice" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.wm.shell.multivalenttests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS
new file mode 100644
index 000000000000..24c1a3a6d400
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+
diff --git a/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
new file mode 100644
index 000000000000..7a0527ccaafb
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
new file mode 100644
index 000000000000..9cd14fca6a9d
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.Insets
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests operations and the resulting state managed by [BubblePositioner]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubblePositionerTest {
+
+ private lateinit var positioner: BubblePositioner
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val defaultDeviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, 1000, 2000),
+ isLargeScreen = false,
+ isSmallTablet = false,
+ isLandscape = false,
+ isRtl = false,
+ insets = Insets.of(0, 0, 0, 0)
+ )
+
+ @Before
+ fun setUp() {
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ positioner = BubblePositioner(context, windowManager)
+ }
+
+ @Test
+ fun testUpdate() {
+ val insets = Insets.of(10, 20, 5, 15)
+ val screenBounds = Rect(0, 0, 1000, 1200)
+ val availableRect = Rect(screenBounds)
+ availableRect.inset(insets)
+ positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
+ assertThat(positioner.availableRect).isEqualTo(availableRect)
+ assertThat(positioner.isLandscape).isFalse()
+ assertThat(positioner.isLargeScreen).isFalse()
+ assertThat(positioner.insets).isEqualTo(insets)
+ }
+
+ @Test
+ fun testShowBubblesVertically_phonePortrait() {
+ positioner.update(defaultDeviceConfig)
+ assertThat(positioner.showBubblesVertically()).isFalse()
+ }
+
+ @Test
+ fun testShowBubblesVertically_phoneLandscape() {
+ positioner.update(defaultDeviceConfig.copy(isLandscape = true))
+ assertThat(positioner.isLandscape).isTrue()
+ assertThat(positioner.showBubblesVertically()).isTrue()
+ }
+
+ @Test
+ fun testShowBubblesVertically_tablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ assertThat(positioner.showBubblesVertically()).isTrue()
+ }
+
+ /** If a resting position hasn't been set, calling it will return the default position. */
+ @Test
+ fun testGetRestingPosition_returnsDefaultPosition() {
+ positioner.update(defaultDeviceConfig)
+ val restingPosition = positioner.getRestingPosition()
+ val defaultPosition = positioner.defaultStartPosition
+ assertThat(restingPosition).isEqualTo(defaultPosition)
+ }
+
+ /** If a resting position has been set, it'll return that instead of the default position. */
+ @Test
+ fun testGetRestingPosition_returnsRestingPosition() {
+ positioner.update(defaultDeviceConfig)
+ val restingPosition = PointF(100f, 100f)
+ positioner.restingPosition = restingPosition
+ assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
+ }
+
+ /** Test that the default resting position on phone is in upper left. */
+ @Test
+ fun testGetRestingPosition_bubble_onPhone() {
+ positioner.update(defaultDeviceConfig)
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_bubble_onPhone_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ /** Test that the default resting position on tablet is middle left. */
+ @Test
+ fun testGetRestingPosition_chatBubble_onTablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_chatBubble_onTablet_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ /** Test that the default resting position on tablet is middle right. */
+ @Test
+ fun testGetDefaultPosition_appBubble_onTablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(startPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_appBubble_onTablet_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(startPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_afterBoundsChange() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true,
+ windowBounds = Rect(0, 0, 2000, 1600)))
+
+ // Set the resting position to the right side
+ var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY())
+ positioner.restingPosition = restingPosition
+
+ // Now make the device smaller
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = false,
+ windowBounds = Rect(0, 0, 1000, 1600)))
+
+ // Check the resting position is on the correct side
+ allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right)
+ }
+
+ @Test
+ fun testHasUserModifiedDefaultPosition_false() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ positioner.restingPosition = positioner.defaultStartPosition
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ }
+
+ @Test
+ fun testHasUserModifiedDefaultPosition_true() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ positioner.restingPosition = PointF(0f, 100f)
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_max() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_customHeight_valid() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+ val minHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+ val bubble =
+ Bubble(
+ "key",
+ ShortcutInfo.Builder(context, "id").build(),
+ minHeight + 100 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor()) {}
+
+ // Ensure the height is the same as the desired value
+ assertThat(positioner.getExpandedViewHeight(bubble))
+ .isEqualTo(bubble.getDesiredHeight(context))
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_customHeight_tooSmall() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val bubble =
+ Bubble(
+ "key",
+ ShortcutInfo.Builder(context, "id").build(),
+ 10 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor()) {}
+
+ // Ensure the height is the same as the desired value
+ val minHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+ assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
+ }
+
+ @Test
+ fun testGetMaxExpandedViewHeight_onLargeTablet() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val manageButtonHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+ val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+ val expandedViewPadding =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+ val expectedHeight =
+ 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
+ assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
+ .isEqualTo(expectedHeight)
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_largeScreen_true() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isTrue()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_smallTablet_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isSmallTablet = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_phone_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testExpandedViewY_phoneLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height so it'll always be top aligned
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_phonePortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // Always top aligned in phone portrait
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_smallTabletLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isSmallTablet = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_smallTabletPortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isSmallTablet = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_largeScreenLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on landscape, large tablet
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_largeScreenPortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ val manageButtonHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+ val manageButtonPlusMargin =
+ manageButtonHeight +
+ 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
+ val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+
+ val expectedExpandedViewY =
+ positioner.availableRect.bottom -
+ manageButtonPlusMargin -
+ positioner.getExpandedViewHeightForLargeScreen() -
+ pointerWidth
+
+ // Bubbles are bottom aligned on portrait, large tablet
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(expectedExpandedViewY)
+ }
+
+ @Test
+ fun testGetTaskViewContentWidth_onLeft() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(true /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
+ @Test
+ fun testGetTaskViewContentWidth_onRight() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(false /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
+ private val defaultYPosition: Float
+ /**
+ * Calculates the Y position bubbles should be placed based on the config. Based on the
+ * calculations in [BubblePositioner.getDefaultStartPosition] and
+ * [BubbleStackView.RelativeStackPosition].
+ */
+ get() {
+ val isTablet = positioner.isLargeScreen
+
+ // On tablet the position is centered, on phone it is an offset from the top.
+ val desiredY =
+ if (isTablet) {
+ positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
+ } else {
+ context.resources
+ .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
+ .toFloat()
+ }
+ // Since we're visually centering the bubbles on tablet, use total screen height rather
+ // than the available height.
+ val height =
+ if (isTablet) {
+ positioner.screenRect.height()
+ } else {
+ positioner.availableRect.height()
+ }
+ val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
+ val allowableStackRegion =
+ positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
new file mode 100644
index 000000000000..8989fc543044
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.view.IWindowManager
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.common.ProtoLog
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/** Unit tests for [BubbleStackView]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleStackViewTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var positioner: BubblePositioner
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var shellExecutor: ShellExecutor
+ private lateinit var windowManager: IWindowManager
+ private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
+ private lateinit var bubbleData: BubbleData
+
+ @Before
+ fun setUp() {
+ // Disable protolog tool when running the tests from studio
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ windowManager = WindowManagerGlobal.getWindowManagerService()!!
+ shellExecutor = TestShellExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ iconFactory =
+ BubbleIconFactory(
+ context,
+ context.resources.getDimensionPixelSize(R.dimen.bubble_size),
+ context.resources.getDimensionPixelSize(R.dimen.bubble_badge_size),
+ Color.BLACK,
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width
+ )
+ )
+ positioner = BubblePositioner(context, windowManager)
+ val bubbleStackViewManager = FakeBubbleStackViewManager()
+ bubbleData =
+ BubbleData(
+ context,
+ BubbleLogger(UiEventLoggerFake()),
+ positioner,
+ BubbleEducationController(context),
+ shellExecutor
+ )
+
+ val sysuiProxy = mock<SysuiProxy>()
+ expandedViewManager = FakeBubbleExpandedViewManager()
+ bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun addBubble() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun tapBubbleToExpand() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ bubble.iconView!!.performClick()
+ // we're checking the expanded state in BubbleData because that's the source of truth. This
+ // will eventually propagate an update back to the stack view, but setting the entire
+ // pipeline is outside the scope of a unit test.
+ assertThat(bubbleData.isExpanded).isTrue()
+ }
+
+ private fun createAndInflateBubble(): Bubble {
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ bubble.setInflateSynchronously(true)
+ bubbleData.notificationEntryUpdated(bubble, true, false)
+
+ val semaphore = Semaphore(0)
+ val callback: BubbleViewInfoTask.Callback =
+ BubbleViewInfoTask.Callback { semaphore.release() }
+ bubble.inflate(
+ callback,
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ positioner,
+ bubbleStackView,
+ null,
+ iconFactory,
+ false
+ )
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubble.isInflated).isTrue()
+ return bubble
+ }
+
+ private class FakeBubbleStackViewManager : BubbleStackViewManager {
+
+ override fun onAllBubblesAnimatedOut() {}
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {}
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {}
+
+ override fun hideCurrentInputMethod() {}
+ }
+
+ private class TestShellExecutor : ShellExecutor {
+
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(r: Runnable, delayMillis: Long) {
+ r.run()
+ }
+
+ override fun removeCallbacks(r: Runnable) {}
+
+ override fun hasCallback(r: Runnable): Boolean = false
+ }
+
+ private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
+ override fun create(): BubbleTaskView {
+ val taskViewTaskController = mock<TaskViewTaskController>()
+ val taskView = TaskView(context, taskViewTaskController)
+ return BubbleTaskView(taskView, shellExecutor)
+ }
+ }
+
+ private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = emptyList()
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {}
+
+ override fun collapseStack() {}
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {}
+
+ override fun removeBubble(key: String, reason: Int) {}
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {}
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+
+ override fun isStackExpanded(): Boolean = false
+
+ override fun isShowingAsBubbleBar(): Boolean = false
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
new file mode 100644
index 000000000000..398fd554f030
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.taskview.TaskView
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleTaskViewTest {
+
+ private lateinit var bubbleTaskView: BubbleTaskView
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var taskView: TaskView
+
+ @Before
+ fun setUp() {
+ taskView = mock()
+ bubbleTaskView = BubbleTaskView(taskView, directExecutor())
+ }
+
+ @Test
+ fun onTaskCreated_updatesState() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(bubbleTaskView.taskId).isEqualTo(123)
+ assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
+ assertThat(bubbleTaskView.isCreated).isTrue()
+ }
+
+ @Test
+ fun onTaskCreated_callsDelegateListener() {
+ var actualTaskId = -1
+ var actualComponentName: ComponentName? = null
+ val delegateListener = object : TaskView.Listener {
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ actualTaskId = taskId
+ actualComponentName = name
+ }
+ }
+ bubbleTaskView.delegateListener = delegateListener
+
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(actualTaskId).isEqualTo(123)
+ assertThat(actualComponentName).isEqualTo(componentName)
+ }
+
+ @Test
+ fun cleanup_invalidTaskId_doesNotRemoveTask() {
+ bubbleTaskView.cleanup()
+ verify(taskView, never()).removeTask()
+ }
+
+ @Test
+ fun cleanup_validTaskId_removesTask() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ bubbleTaskView.cleanup()
+ verify(taskView).removeTask()
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTestsForDevice b/libs/WindowManager/Shell/multivalentTestsForDevice
new file mode 120000
index 000000000000..20ee34ada103
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests \ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentTestsForDeviceless b/libs/WindowManager/Shell/multivalentTestsForDeviceless
new file mode 120000
index 000000000000..20ee34ada103
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml
new file mode 100644
index 000000000000..52a59671baa1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_caption_button_on_hover_dark"/>
+ <item android:color="@color/desktop_mode_caption_button"/>
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml
new file mode 100644
index 000000000000..6d8a51cd6f8f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_caption_button_on_hover_light"/>
+ <item android:color="@color/desktop_mode_caption_button"/>
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
new file mode 100644
index 000000000000..948264579e1d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/progress">
+ <rotate
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fromDegrees="275"
+ android:toDegrees="275">
+ <shape
+ android:shape="ring"
+ android:thickness="3dp"
+ android:innerRadius="17dp"
+ android:useLevel="true">
+ </shape>
+ </rotate>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_split.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml
index 2cfdf6ed259b..ff49edb7a699 100644
--- a/libs/WindowManager/Shell/res/drawable/pip_split.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@
~ 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="@dimen/pip_expand_action_inner_size"
- android:height="@dimen/pip_expand_action_inner_size"
- android:viewportWidth="24"
- android:viewportHeight="24">
-
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
<path
- android:fillColor="#FFFFFF"
- android:pathData="M20,18h-5V6h5V18z M22,18V6c0-1.1-0.9-2-2-2h-5c-1.1,0-2,0.9-2,2v12c0,1.1,0.9,2,2,2h5C21.1,20,22,19.1,22,18z M9,18H4V6h5
- V18z M11,18V6c0-1.1-0.9-2-2-2H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h5C10.1,20,11,19.1,11,18z" />
+ android:fillColor="#FF000000"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/rounded_button.xml b/libs/WindowManager/Shell/res/drawable/rounded_button.xml
new file mode 100644
index 000000000000..17a0bab56a74
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/rounded_button.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="20dp" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 681a52bea2b2..34f03c2f226b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -21,4 +21,10 @@
android:orientation="vertical"
android:id="@+id/bubble_bar_expanded_view">
+ <com.android.wm.shell.bubbles.bar.BubbleBarHandleView
+ android:id="@+id/bubble_bar_handle_view"
+ android:layout_height="@dimen/bubble_bar_expanded_view_caption_height"
+ android:layout_width="@dimen/bubble_bar_expanded_view_caption_width"
+ android:layout_gravity="top|center_horizontal" />
+
</com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1e4dca..a5605a7ff50a 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -16,6 +16,7 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/desktop_mode_caption"
android:layout_width="match_parent"
@@ -27,31 +28,33 @@
android:id="@+id/open_menu_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="16dp">
-
+ android:paddingStart="12dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_gravity="center_vertical"
- android:contentDescription="@string/app_icon_text" />
+ android:contentDescription="@string/app_icon_text"
+ android:layout_marginStart="6dp"
+ android:scaleType="centerCrop"/>
<TextView
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="20dp"
- android:minWidth="80dp"
+ android:maxWidth="86dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
android:lineHeight="20dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
+ android:layout_marginStart="8dp"
tools:text="Gmail"/>
<ImageButton
@@ -64,6 +67,7 @@
android:scaleType="fitCenter"
android:clickable="false"
android:focusable="false"
+ android:layout_marginHorizontal="8dp"
android:layout_gravity="center_vertical"/>
</LinearLayout>
@@ -74,27 +78,25 @@
android:layout_height="40dp"
android:layout_weight="1"/>
- <ImageButton
- android:id="@+id/maximize_window"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:padding="9dp"
- android:layout_marginEnd="8dp"
- android:contentDescription="@string/maximize_button_text"
- android:src="@drawable/decor_desktop_mode_maximize_button_dark"
- android:scaleType="fitCenter"
- android:gravity="end"
- android:background="@null"/>
+ <com.android.wm.shell.windowdecor.MaximizeButtonView
+ android:id="@+id/maximize_button_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:clickable="true"
+ android:focusable="true" />
<ImageButton
android:id="@+id/close_window"
- android:layout_width="40dp"
+ android:layout_width="44dp"
android:layout_height="40dp"
- android:padding="4dp"
+ android:paddingHorizontal="10dp"
+ android:paddingVertical="8dp"
android:layout_marginEnd="8dp"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"
android:contentDescription="@string/close_button_text"
- android:src="@drawable/decor_close_button_dark"
- android:scaleType="fitCenter"
- android:gravity="end"
- android:background="@null"/>
+ android:src="@drawable/desktop_mode_header_ic_close"
+ android:scaleType="centerCrop"
+ android:gravity="end"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index cec7ee233236..ef7478c04dda 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -18,13 +18,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/desktop_mode_caption"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<ImageButton
android:id="@+id/caption_handle"
- android:layout_width="128dp"
+ android:layout_width="@dimen/desktop_mode_fullscreen_decor_caption_width"
android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
android:paddingVertical="16dp"
android:contentDescription="@string/handle_text"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 0db72f7be8e6..dbfd6e5d8d94 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/maximize_menu"
style="?android:attr/buttonBarStyle"
android:layout_width="@dimen/desktop_mode_maximize_menu_width"
android:layout_height="@dimen/desktop_mode_maximize_menu_height"
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
new file mode 100644
index 000000000000..e0057fe64fd2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ProgressBar
+ android:id="@+id/progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:progressDrawable="@drawable/circular_progress"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:indeterminate="false"
+ android:visibility="invisible"/>
+
+ <ImageButton
+ android:id="@+id/maximize_window"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:padding="9dp"
+ android:contentDescription="@string/maximize_button_text"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:src="@drawable/decor_desktop_mode_maximize_button_dark"
+ android:scaleType="fitCenter" />
+</merge> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index 1dd17bad155b..258f50629ccd 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -79,16 +79,6 @@
android:src="@drawable/pip_ic_settings"
android:background="?android:selectableItemBackgroundBorderless" />
- <ImageButton
- android:id="@+id/enter_split"
- android:layout_width="@dimen/pip_split_icon_size"
- android:layout_height="@dimen/pip_split_icon_size"
- android:layout_gravity="top|start"
- android:layout_margin="@dimen/pip_split_icon_margin"
- android:gravity="center"
- android:contentDescription="@string/pip_phone_enter_split"
- android:src="@drawable/pip_split"
- android:background="?android:selectableItemBackgroundBorderless" />
</LinearLayout>
<!--TODO (b/156917828): Add content description for a11y purposes?-->
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 82a358cf68d6..ba064ff71f6d 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -49,7 +49,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:padding="@dimen/pip_menu_button_start_end_offset"
android:clipToPadding="false"
android:alpha="0"
android:contentDescription="@string/a11y_pip_menu_entered"/>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 90b13cdc79b4..dd6f8455f82a 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Maak toe"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Vou uit"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellings"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gaan by verdeelde skerm in"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Kieslys"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Prent-in-prent-kieslys"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in beeld-in-beeld"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 478585aace68..18a4ccf5c16d 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ዝጋ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ዘርጋ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ቅንብሮች"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"የተከፈለ ማያ ገጽን አስገባ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ምናሌ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"የሥዕል-ላይ-ሥዕል ምናሌ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በሥዕል-ላይ-ሥዕል ውስጥ ነው"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index b2a522c89f8c..7ca335e4a655 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"إغلاق"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"توسيع"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"الإعدادات"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"الدخول في وضع تقسيم الشاشة"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"القائمة"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"قائمة نافذة ضمن النافذة"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> يظهر في صورة داخل صورة"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 897c38f66e4b..944c4f25bf2a 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"বন্ধ কৰক"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"বিস্তাৰ কৰক"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ছেটিং"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"বিভাজিত স্ক্ৰীন ম’ডলৈ যাওক"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"চিত্ৰৰ ভিতৰৰ চিত্ৰ মেনু"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> চিত্ৰৰ ভিতৰৰ চিত্ৰত আছে"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 4854e0db7ed5..c320e415d604 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Bağlayın"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Genişləndirin"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana daxil olun"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Şəkildə Şəkil Menyusu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> şəkil içində şəkildədir"</string>
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 cac4e67b1cfc..19ca4d300b7e 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Podešavanja"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Uđi na podeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni slike u slici."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je slika u slici"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index cac76df15910..74ae1d7637d0 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрыць"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Разгарнуць"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Налады"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Падзяліць экран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню рэжыму \"Відарыс у відарысе\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> з’яўляецца відарысам у відарысе"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index ac9a20806b72..1b753f5359ba 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затваряне"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Разгъване"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Преминаване към разделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню за режима „Картина в картината“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е в режима „Картина в картината“"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 3b83dcb461ee..2ea22cc1455a 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"বন্ধ করুন"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"বড় করুন"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"সেটিংস"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"\'স্প্লিট স্ক্রিন\' মোড চালু করুন"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ছবির-মধ্যে-ছবি মেনু"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"ছবির-মধ্যে-ছবি তে <xliff:g id="NAME">%s</xliff:g> আছেন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 813d163d07fe..13655b3c5c85 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvori podijeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni načina rada slike u slici"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je u načinu priakza Slika u slici"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index d00c50bb0294..cb897c5b612d 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tanca"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Desplega"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuració"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entra al mode de pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú d\'imatge sobre imatge"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en mode d\'imatge sobre imatge"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 40132e4f67f8..ded2707afd1d 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zavřít"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozbalit"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavení"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivovat rozdělenou obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Nabídka"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Nabídka režimu obrazu v obraze"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikace <xliff:g id="NAME">%s</xliff:g> je v režimu obraz v obraze"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 6e9738dc7398..2bdb29d67447 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Luk"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Udvid"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Indstillinger"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Åbn opdelt skærm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu for integreret billede"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> vises som integreret billede"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 5da224d35360..e99d9d0c269a 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Splitscreen aktivieren"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü „Bild im Bild“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 822b5526c6fd..d8bb740535fc 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Κλείσιμο"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Ανάπτυξη"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ρυθμίσεις"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Μετάβαση σε διαχωρισμό οθόνης"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Μενού"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Μενού λειτουργίας Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Η λειτουργία picture-in-picture είναι ενεργή σε <xliff:g id="NAME">%s</xliff:g>."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 76464b398f89..5e1b274705dd 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index c0c46cd608ee..2525b321d9d7 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 76464b398f89..5e1b274705dd 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 76464b398f89..5e1b274705dd 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index f089938fd9cb..0623bef925e2 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎Close‎‏‎‎‏‎"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎Expand‎‏‎‎‏‎"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎Settings‎‏‎‎‏‎"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎Enter split screen‎‏‎‎‏‎"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎Menu‎‏‎‎‏‎"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎Picture-in-Picture Menu‎‏‎‎‏‎"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ is in picture-in-picture‎‏‎‎‏‎"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 6bbc1e37671f..9fe77ddf7e28 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Cerrar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla en pantalla"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en modo de Pantalla en pantalla"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index c662ff603dd7..b88f215eb54e 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Cerrar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Mostrar"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ajustes"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de imagen en imagen"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en imagen en imagen"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index ade5e2d18645..529b6d10b3c6 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sule"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Laiendamine"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Seaded"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ava jagatud ekraanikuva"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menüü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menüü Pilt pildis"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on režiimis Pilt pildis"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index d6cb66885cf5..7438f4240eae 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Itxi"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Zabaldu"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ezarpenak"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Sartu pantaila zatituan"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menua"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pantaila txiki gainjarriaren menua"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Pantaila txiki gainjarrian dago <xliff:g id="NAME">%s</xliff:g>"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index ba0f51cb1490..f7fcb2162603 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"بستن"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"بزرگ کردن"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"تنظیمات"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ورود به حالت «صفحهٔ دونیمه»"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"منو"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"منو تصویر در تصویر"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> درحالت تصویر در تصویر است"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index a53f861e1d18..400107317637 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sulje"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Laajenna"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Asetukset"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Avaa jaettu näyttö"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valikko"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kuva kuvassa ‑valikko"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on kuva kuvassa ‑tilassa"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 4563556657af..a883e087cb3b 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fermer"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Développer"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entrer dans l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu d\'incrustation d\'image"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode d\'incrustation d\'image"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 895757184b23..357ff91df06d 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fermer"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Développer"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accéder à l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu \"Picture-in-picture\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode Picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 54c864eced47..a62190754129 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Pechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Despregar"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Inserir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla superposta"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está na pantalla superposta"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 2b092795b035..43c178f50d15 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"બંધ કરો"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"વિસ્તૃત કરો"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"સેટિંગ"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"વિભાજિત સ્ક્રીન મોડમાં દાખલ થાઓ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"મેનૂ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ચિત્રમાં ચિત્ર મેનૂ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ચિત્રમાં-ચિત્રની અંદર છે"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 35b099ac6d38..9f6a57fa0d73 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बंद करें"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तार करें"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन मोड में जाएं"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेन्यू"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"पिक्चर में पिक्चर मेन्यू"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"पिक्चर में पिक्चर\" के अंदर है"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index f2c3c22414df..4378c5642f5c 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširivanje"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvorite podijeljeni zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izbornik"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izbornik slike u slici"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> jest na slici u slici"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index d94bb29f1d73..e5f199fea647 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Bezárás"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Kibontás"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Beállítások"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Váltás osztott képernyőre"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kép a képben menü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A(z) <xliff:g id="NAME">%s</xliff:g> kép a képben funkciót használ"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index f2945c16e50b..e0a5afe13827 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Փակել"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Ընդարձակել"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Կարգավորումներ"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Մտնել տրոհված էկրանի ռեժիմ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ընտրացանկ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"«Նկար նկարի մեջ» ռեժիմի ընտրացանկ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>-ը «Նկար նկարի մեջ» ռեժիմում է"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index c39b429e489c..802583771852 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tutup"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Luaskan"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setelan"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk ke mode layar terpisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> adalah picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 630eaa3855c1..cece56ec960f 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Loka"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Stækka"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Stillingar"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Opna skjáskiptingu"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valmynd"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Valmynd fyrir mynd í mynd"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er með mynd í mynd"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 77893c9e38cf..731db8c12825 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Chiudi"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Espandi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Impostazioni"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accedi a schermo diviso"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture in picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> è in Picture in picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 4f28c23ba5df..adf55f3696c3 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"סגירה"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"הרחבה"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"כניסה למסך המפוצל"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"תפריט \'תמונה בתוך תמונה\'"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 60b4d7eb8b4d..35432229dc7b 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"閉じる"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"分割画面に切り替え"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"メニュー"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ピクチャー イン ピクチャーのメニュー"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>はピクチャー イン ピクチャーで表示中です"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 28d2257786a7..1e6e657b5cf8 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"დახურვა"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"გაშლა"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"პარამეტრები"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"გაყოფილ ეკრანში შესვლა"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"მენიუ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"„ეკრანი ეკრანში“ რეჟიმის მენიუ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> იყენებს რეჟიმს „ეკრანი ეკრანში“"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 441df8d70e95..6d9ff26132ea 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Жабу"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Жаю"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Бөлінген экранға кіру"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Mәзір"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"Сурет ішіндегі сурет\" мәзірі"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"суреттегі сурет\" режимінде"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index efa6418eaa47..586ef7327a70 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"បិទ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ពង្រីក"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ការកំណត់"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ចូលមុខងារ​បំបែកអេក្រង់"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ម៉ឺនុយ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ម៉ឺនុយ​រូប​ក្នុងរូប"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ស្ថិតក្នុងមុខងាររូបក្នុងរូប"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 0cda44509b54..78ca0c7979a9 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ಮುಚ್ಚಿ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ವಿಸ್ತೃತಗೊಳಿಸು"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ಸ್ಪ್ಲಿಟ್‌-ಸ್ಕ್ರೀನ್‌ಗೆ ಪ್ರವೇಶಿಸಿ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವಾಗಿದೆ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 676506fc68dd..70aa3767d7dd 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"닫기"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"펼치기"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"설정"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"화면 분할 모드로 전환"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"메뉴"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"PIP 모드 메뉴"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>에서 PIP 사용 중"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 57253ef55085..b2a0a49b401a 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Жабуу"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Жайып көрсөтүү"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Экранды бөлүү режимине өтүү"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Сүрөт ичиндеги сүрөт менюсу"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index c5f6e2245b31..1cbdbd412631 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ປິດ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ຂະຫຍາຍ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ການຕັ້ງຄ່າ"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ເຂົ້າການແບ່ງໜ້າຈໍ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ເມນູ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ເມນູການສະແດງຜົນຊ້ອນກັນ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ແມ່ນເປັນການສະແດງຜົນຫຼາຍຢ່າງພ້ອມກັນ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index eeed5a416fdc..d154c57704a1 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Uždaryti"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Išskleisti"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nustatymai"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Įjungti išskaidyto ekrano režimą"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Vaizdo vaizde meniu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> rodom. vaizdo vaizde"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 4324d468042b..ce269503ceef 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Aizvērt"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Izvērst"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Iestatījumi"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Piekļūt ekrāna sadalīšanas režīmam"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izvēlne"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izvēlne attēlam attēlā"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ir attēlā attēlā"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 471f5bdfcf1a..9d69c50626be 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затвори"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Проширете"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Поставки"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Влези во поделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени за „Слика во слика“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е во слика во слика"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 5bc694a10747..c0e83386d572 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"അവസാനിപ്പിക്കുക"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"വികസിപ്പിക്കുക"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ക്രമീകരണം"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"സ്ക്രീൻ വിഭജന മോഡിൽ പ്രവേശിക്കുക"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"മെനു"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ചിത്രത്തിനുള്ളിൽ ചിത്രം മെനു"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ചിത്രത്തിനുള്ളിൽ ചിത്രം രീതിയിലാണ്"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 0268c649380d..ba5d283fb8a7 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Хаах"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Дэлгэх"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Тохиргоо"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Хуваасан дэлгэцийг оруулна уу"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Цэс"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Дэлгэц доторх дэлгэцийн цэс"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> дэлгэцэн доторх дэлгэцэд байна"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 2e6163e65668..17601c18366a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बंद करा"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तृत करा"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग्ज"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन एंटर करा"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनू"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"चित्रात-चित्र मेनू"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> चित्रामध्ये चित्र मध्ये आहे"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index a60e61b892cb..d5547fa8a056 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tutup"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Kembangkan"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Tetapan"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk skrin pisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Gambar dalam Gambar"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> terdapat dalam gambar dalam gambar"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 6b91d4676621..07bfc990d62d 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ပိတ်ရန်"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ချဲ့ရန်"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ဆက်တင်များ"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသို့ ဝင်ရန်"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"မီနူး"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"နှစ်ခုထပ်၍ ကြည့်ခြင်းမီနူး"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> သည် နှစ်ခုထပ်၍ကြည့်ခြင်း ဖွင့်ထားသည်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index ec9ece3484b8..f609d019daae 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Lukk"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Vis"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Innstillinger"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivér delt skjerm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bilde-i-bilde-meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er i bilde-i-bilde"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 8bb07be12c48..f7d49908a9f7 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बन्द गर्नुहोस्"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तृत गर्नुहोस्"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिङहरू"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रिन मोड प्रयोग गर्नुहोस्"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनु"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"picture-in-picture\" मेनु"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> Picture-in-picture मा छ"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index c6c60ae4b1f2..a38cb7547385 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sluiten"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Uitvouwen"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellingen"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gesplitst scherm openen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Scherm-in-scherm-menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in scherm-in-scherm"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 927dde40134d..e3097beb6166 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ବଢ଼ାନ୍ତୁ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ସେଟିଂସ୍"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ମୋଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ମେନୁ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ପିକଚର-ଇନ-ପିକଚର ମେନୁ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 0e12fb872005..3aea6f69749f 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ਬੰਦ ਕਰੋ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ਸੈਟਿੰਗਾਂ"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ਮੀਨੂ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਮੀਨੂ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ਤਸਵੀਰ-ਅੰਦਰ-ਤਸਵੀਰ ਵਿੱਚ ਹੈ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 75a8ce6bc16d..aec3722cefa5 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zamknij"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozwiń"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ustawienia"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Włącz podzielony ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu funkcji Obraz w obrazie."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikacja <xliff:g id="NAME">%s</xliff:g> działa w trybie obraz w obrazie"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index b84a0ded4939..49935ada4656 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Abrir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index d84bfcdd73ff..f636da7997eb 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Definições"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aceder ao ecrã dividido"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu de ecrã no ecrã"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> está no modo de ecrã no ecrã"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index b84a0ded4939..49935ada4656 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Abrir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index eeea428cc8fa..c20f350ae198 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Închide"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meniu picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 26b0f94eb585..49347d2d8086 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрыть"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Развернуть"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Включить разделение экрана"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинке\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> находится в режиме \"Картинка в картинке\""</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 9b9a430ce73f..e5a974683e49 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"වසන්න"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"දිග හරින්න"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"සැකසීම්"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"බෙදුම් තිරයට ඇතුළු වන්න"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"මෙනුව"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"පින්තූරය තුළ පින්තූරය මෙනුව"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> පින්තූරය-තුළ-පින්තූරය තුළ වේ"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4b2153180cbe..c2d20ddb0d3b 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zavrieť"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozbaliť"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavenia"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Prejsť na rozdelenú obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ponuka"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Ponuka obrazu v obraze"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v režime obraz v obraze"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 581cf5b815c6..cfe4480c6e1a 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zapri"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Razširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavitve"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Vklopi razdeljen zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni za sliko v sliki"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v načinu slika v sliki"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 9dc7b7ebef99..cba98c2fb61a 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Mbyll"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Zgjero"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Cilësimet"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Hyr në ekranin e ndarë"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyja"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyja e \"Figurës brenda figurës\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> është në figurë brenda figurës"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index cd532f79dd78..5031f5bf5d64 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затвори"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Прошири"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Подешавања"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Уђи на подељени екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени слике у слици."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> је слика у слици"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 386dda7e088d..742be37b67ef 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Stäng"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Utöka"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Inställningar"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Starta delad skärm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bild-i-bild-meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> visas i bild-i-bild"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 69b2e34ada3c..68a7262d0eaf 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Funga"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Panua"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Mipangilio"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Weka skrini iliyogawanywa"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyu ya kipengele cha Kupachika Picha ndani ya Picha nyingine."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> iko katika hali ya picha ndani ya picha nyingine"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 037b5aba22f5..fe8fa057dc95 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"மூடு"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"விரி"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"அமைப்புகள்"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"திரைப் பிரிப்பு பயன்முறைக்குச் செல்"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"மெனு"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"பிக்ச்சர்-இன்-பிக்ச்சர் மெனு"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> தற்போது பிக்ச்சர்-இன்-பிக்ச்சரில் உள்ளது"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 694ecb951210..593c8fc28a0d 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"మూసివేయి"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"విస్తరింపజేయి"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"సెట్టింగ్‌లు"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"స్ప్లిట్ స్క్రీన్‌ను ఎంటర్ చేయండి"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"మెనూ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"పిక్చర్-ఇన్-పిక్చర్ మెనూ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> చిత్రంలో చిత్రం రూపంలో ఉంది"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index d4b6aff2ee7d..cd3bf6a626c0 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ปิด"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ขยาย"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"การตั้งค่า"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"เข้าสู่โหมดแบ่งหน้าจอ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"เมนู"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"เมนูการแสดงภาพซ้อนภาพ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ใช้การแสดงภาพซ้อนภาพ"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index db9303c0fd9c..bf05e149ea57 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Isara"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Palawakin"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Mga Setting"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Pumasok sa split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu ng Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Nasa picture-in-picture ang <xliff:g id="NAME">%s</xliff:g>"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 818666c79973..2dfa38a76dfa 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Kapat"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Genişlet"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana geç"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pencere içinde pencere menüsü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>, pencere içinde pencere özelliğini kullanıyor"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 85fb8e114476..57ca64f5b159 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрити"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Розгорнути"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Налаштування"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Розділити екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинці\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"У додатку <xliff:g id="NAME">%s</xliff:g> є функція \"Картинка в картинці\""</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 813870b134b4..077037373aa2 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"بند کریں"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"پھیلائیں"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"مینیو"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینیو"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> تصویر میں تصویر میں ہے"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 7bcacbb93f1f..e2d1f47c210b 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Yopish"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Yoyish"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Sozlamalar"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ajratilgan ekranga kirish"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Tasvir ustida tasvir menyusi"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> tasvir ustida tasvir rejimida"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 416dd91162c2..4608b2b10c3f 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Đóng"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Mở rộng"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Cài đặt"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Truy cập chế độ chia đôi màn hình"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Trình đơn hình trong hình."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> đang ở chế độ ảnh trong ảnh"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 6ad172807f6a..cbb857c58611 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"关闭"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展开"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"设置"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"进入分屏模式"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"菜单"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"画中画菜单"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>目前位于“画中画”中"</string>
@@ -57,7 +56,7 @@
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"启动单手模式"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"退出单手模式"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>对话泡的设置"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>消息气泡的设置"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"菜单"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"重新加入叠放"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g>:<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
@@ -69,22 +68,22 @@
<string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"展开“<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
<string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"收起“<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
- <string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以对话泡形式显示对话"</string>
- <string name="bubbles_user_education_title" msgid="2112319053732691899">"使用对话泡聊天"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"新对话会以浮动图标或对话泡形式显示。点按即可打开对话泡。拖动即可移动对话泡。"</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"随时控制对话泡"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"点按“管理”按钮,可关闭来自此应用的对话泡"</string>
+ <string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭消息气泡"</string>
+ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以消息气泡形式显示对话"</string>
+ <string name="bubbles_user_education_title" msgid="2112319053732691899">"使用消息气泡聊天"</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"新对话会以浮动图标或消息气泡形式显示。点按即可打开消息气泡。拖动即可移动消息气泡。"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"随时控制消息气泡"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"点按“管理”按钮,可关闭来自此应用的消息气泡"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"知道了"</string>
- <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近没有对话泡"</string>
- <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"此处会显示最近的对话泡和已关闭的对话泡"</string>
- <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"使用对话泡聊天"</string>
+ <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近没有消息气泡"</string>
+ <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"此处会显示最近的消息气泡和已关闭的消息气泡"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"使用消息气泡聊天"</string>
<string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新对话会以图标形式显示在屏幕底部的角落中。点按图标即可展开对话,拖动图标即可关闭对话。"</string>
- <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"随时控制对话泡"</string>
- <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"点按此处即可管理哪些应用和对话可以显示对话泡"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"随时控制消息气泡"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"点按此处即可管理哪些应用和对话可以显示消息气泡"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
- <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭对话泡。"</string>
+ <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭消息气泡。"</string>
<string name="restart_button_description" msgid="4564728020654658478">"点按即可重启此应用,获得更好的视觉体验"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"在“设置”中更改此应用的宽高比"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"更改高宽比"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index b5b94ec40fd1..d89b2c29216e 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"關閉"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割螢幕"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在畫中畫模式"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 0f2a052dbbe0..4ce50a44e12a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"關閉"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割畫面"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"子母畫面選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在子母畫面中"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index a696f9ec6251..fa680f6eee89 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -20,7 +20,6 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Amasethingi"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Imenyu Yesithombe-Esithombeni"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index fae71efe3b39..758dbfd5f3c5 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -66,4 +66,9 @@
<color name="desktop_mode_maximize_menu_button_outline">#797869</color>
<color name="desktop_mode_maximize_menu_button_outline_on_hover">#606219</color>
<color name="desktop_mode_maximize_menu_button_on_hover">#E7E790</color>
+ <color name="desktop_mode_maximize_menu_progress_light">#33000000</color>
+ <color name="desktop_mode_maximize_menu_progress_dark">#33FFFFFF</color>
+ <color name="desktop_mode_caption_button_on_hover_light">#11000000</color>
+ <color name="desktop_mode_caption_button_on_hover_dark">#11FFFFFF</color>
+ <color name="desktop_mode_caption_button">#00000000</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae48c8fd..a541c590575f 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,15 +45,9 @@
<!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
<bool name="config_pipEnableResizeForMenu">true</bool>
- <!-- Allow PIP to resize via dragging the corner of PiP. -->
- <bool name="config_pipEnableDragCornerResize">false</bool>
-
<!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
<fraction name="config_pipShortestEdgePercent">40%</fraction>
- <!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. -->
- <bool name="config_pipEnableEnterSplitButton">false</bool>
-
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">1000</integer>
@@ -134,6 +128,13 @@
<!-- Whether the additional education about reachability is enabled -->
<bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
+ <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+ size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ 100 is the default value where the activity has to fit exactly within the task to allow
+ size compat restart button to be hidden. 0 means size compat restart button will always
+ be hidden. -->
+ <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
<!-- Whether DragAndDrop capability is enabled -->
<bool name="config_enableShellDragDrop">true</bool>
@@ -144,4 +145,7 @@
<!-- Whether CompatUIController is enabled -->
<bool name="config_enableCompatUIController">true</bool>
+
+ <!-- Whether pointer pilfer is required to start back animation. -->
+ <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de6168bc7..7dd39613b438 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -67,10 +67,6 @@
<dimen name="pip_resize_handle_margin">4dp</dimen>
<dimen name="pip_resize_handle_padding">0dp</dimen>
- <!-- PIP Split icon size and margin. -->
- <dimen name="pip_split_icon_size">24dp</dimen>
- <dimen name="pip_split_icon_margin">12dp</dimen>
-
<!-- PIP stash offset size, which is the width of visible PIP region when stashed. -->
<dimen name="pip_stash_offset">32dp</dimen>
@@ -105,6 +101,10 @@
<dimen name="split_divider_bar_width">10dp</dimen>
<dimen name="split_divider_corner_size">42dp</dimen>
+ <!-- The distance from the edge of the screen to invoke splitscreen when the user is dragging
+ an intent that can be launched into split. -->
+ <dimen name="drag_launchable_intent_edge_margin">48dp</dimen>
+
<!-- One-Handed Mode -->
<!-- Threshold for dragging distance to enable one-handed mode -->
<dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
@@ -248,6 +248,8 @@
<dimen name="bubble_popup_padding">24dp</dimen>
<!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
<dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <!-- The width of the caption bar at the top of bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen>
<!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
<dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
<!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
@@ -266,6 +268,10 @@
<dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
<!-- Size of the icons in the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
+ <!-- Corner radius for expanded view when bubble bar is used -->
+ <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen>
+ <!-- Corner radius for expanded view while it is being dragged -->
+ <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
@@ -413,6 +419,38 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+ <!-- Width of desktop mode caption for fullscreen tasks. -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_width">128dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks. -->
+ <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+ <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+ <!-- 32dp width back button + 10dp margin -->
+ <dimen name="caption_left_buttons_width">32dp</dimen>
+
+ <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+ <dimen name="caption_right_buttons_width">126dp</dimen>
+
+ <!-- 2 buttons * 44dp button size + 16dp total margins. -->
+ <dimen name="desktop_mode_right_edge_buttons_width">104dp</dimen>
+
+ <!-- 22dp padding + 24dp app icon + 16dp expand button.
+ Text varies in size, we will calculate that width separately. -->
+ <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
+ <!-- When custom headers are requested, this is the width of the left-aligned region that is
+ taken up by caption elements and extra margins. The customizable region starts at the
+ end of this area. -->
+ <dimen name="desktop_mode_customizable_caption_margin_start">84dp</dimen>
+
+ <!-- When custom headers are requested, this is the width of the right-aligned region that is
+ taken up by caption elements and extra margins. The customizable region ends at the
+ start of this area. -->
+ <dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
@@ -459,7 +497,7 @@
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
<!-- The radius of the caption menu icon. -->
- <dimen name="desktop_mode_caption_icon_radius">28dp</dimen>
+ <dimen name="desktop_mode_caption_icon_radius">24dp</dimen>
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
@@ -472,9 +510,16 @@
split select if dragged until the touch input is within the range. -->
<dimen name="desktop_mode_transition_area_width">32dp</dimen>
- <!-- The height of the area at the top of the screen where a freeform task will transition to
- fullscreen if dragged until the top bound of the task is within the area. -->
- <dimen name="desktop_mode_transition_area_height">16dp</dimen>
+ <!-- The width of the area where a desktop task will transition to fullscreen. -->
+ <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
+
+ <!-- The height of the area where a desktop task will transition to fullscreen. -->
+ <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen>
+
+ <!-- The height on the screen where drag to the left or right edge will result in a
+ desktop task snapping to split size. The empty space between this and the top is to allow
+ for corner drags without transition. -->
+ <dimen name="desktop_mode_split_from_desktop_height">100dp</dimen>
<!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
<item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 3e66bbbf8d39..812a81ba33d1 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -24,9 +24,6 @@
<!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
<string name="pip_phone_settings">Settings</string>
- <!-- Label for the PIP enter split button [CHAR LIMIT=NONE] -->
- <string name="pip_phone_enter_split">Enter split screen</string>
-
<!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
<string name="pip_menu_title">Menu</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
index 7e95814c06c2..fd3a749af284 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.graphics.Point;
import android.util.RotationUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 936faa3ee6bf..dcd4062cb819 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
@@ -28,14 +28,13 @@ 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_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -53,6 +52,8 @@ import java.util.function.Predicate;
/** Various utility functions for transitions. */
public class TransitionUtil {
+ /** 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;
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index 88525aabe53b..ef9bf008b294 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -16,7 +16,10 @@
package com.android.wm.shell;
-import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -24,19 +27,19 @@ import java.io.PrintWriter;
import java.util.Arrays;
/**
- * Controls the {@link ShellProtoLogImpl} in WMShell via adb shell commands.
+ * Controls the {@link ProtoLog} in WMShell via adb shell commands.
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell protolog ...}.
*/
public class ProtoLogController implements ShellCommandHandler.ShellCommandActionHandler {
private final ShellCommandHandler mShellCommandHandler;
- private final ShellProtoLogImpl mShellProtoLog;
+ private final IProtoLog mShellProtoLog;
public ProtoLogController(ShellInit shellInit,
ShellCommandHandler shellCommandHandler) {
shellInit.addInitCallback(this::onInit, this);
mShellCommandHandler = shellCommandHandler;
- mShellProtoLog = ShellProtoLogImpl.getSingleInstance();
+ mShellProtoLog = ProtoLog.getSingleInstance();
}
void onInit() {
@@ -45,22 +48,35 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
+ final ILogger logger = pw::println;
switch (args[0]) {
case "status": {
- pw.println(mShellProtoLog.getStatus());
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).getStatus();
return true;
}
case "start": {
- mShellProtoLog.startProtoLog(pw);
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).startProtoLog(pw);
return true;
}
case "stop": {
- mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).stopProtoLog(pw, true);
return true;
}
case "enable-text": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- int result = mShellProtoLog.startTextLogging(groups, pw);
+ int result = mShellProtoLog.startLoggingToLogcat(groups, logger);
if (result == 0) {
pw.println("Starting logging on groups: " + Arrays.toString(groups));
return true;
@@ -69,7 +85,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
}
case "disable-text": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- int result = mShellProtoLog.stopTextLogging(groups, pw);
+ int result = mShellProtoLog.stopLoggingToLogcat(groups, logger);
if (result == 0) {
pw.println("Stopping logging on groups: " + Arrays.toString(groups));
return true;
@@ -78,19 +94,23 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
}
case "enable": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- return mShellProtoLog.startTextLogging(groups, pw) == 0;
+ return mShellProtoLog.startLoggingToLogcat(groups, logger) == 0;
}
case "disable": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- return mShellProtoLog.stopTextLogging(groups, pw) == 0;
+ return mShellProtoLog.stopLoggingToLogcat(groups, logger) == 0;
}
case "save-for-bugreport": {
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command");
+ return false;
+ }
if (!mShellProtoLog.isProtoEnabled()) {
pw.println("Logging to proto is not enabled for WMShell.");
return false;
}
- mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
- mShellProtoLog.startProtoLog(pw);
+ ((LegacyProtoLogImpl) mShellProtoLog).stopProtoLog(pw, true /* writeToFile */);
+ ((LegacyProtoLogImpl) mShellProtoLog).startProtoLog(pw);
return true;
}
default: {
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 fe65fdd30e48..d8d0d876b4f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -643,19 +643,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
- /** Helper to set int metadata on the Surface corresponding to the task id. */
- public void setSurfaceMetadata(int taskId, int key, int value) {
- synchronized (mLock) {
- final TaskAppearedInfo info = mTasks.get(taskId);
- if (info == null || info.getLeash() == null) {
- return;
- }
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setMetadata(info.getLeash(), key, value);
- t.apply();
- }
- }
-
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8241e1a481ee..8d30db64a3e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index ac75c73d7e6d..539832e3cf3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
@@ -45,7 +46,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -330,6 +331,11 @@ class ActivityEmbeddingAnimationRunner {
if (!animation.hasExtension()) {
continue;
}
+ if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
+ && adapter.mChange.getActivityComponent() != null) {
+ // Skip edge extension for translucent activity.
+ continue;
+ }
final TransitionInfo.Change change = adapter.mChange;
if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index efa5a1a64ade..0272f1cda6ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -38,7 +38,7 @@ import android.view.animation.TranslateAnimation;
import android.window.TransitionInfo;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4e852cfaa48..1f9358e2aa91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -38,9 +38,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
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 2ec9e8b12fc6..19963675ff86 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
@@ -71,6 +71,13 @@ public class Interpolators {
*/
public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
index e06d3ef4e1ab..5b0de5070a60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -21,5 +21,4 @@ package com.android.wm.shell.back;
*/
class BackAnimationConstants {
static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
- static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d8c691b01b61..9bd8531d33dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.back;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -64,15 +64,18 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.R;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -112,6 +115,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
+ private boolean mPointerPilfered = false;
+ private final boolean mRequirePointerPilfer;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -124,6 +129,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@@ -173,6 +179,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private StatusBarCustomizer mCustomizer;
private boolean mTrackingLatency;
+ // Keep previous navigation type before remove mBackNavigationInfo.
+ @BackNavigationInfo.BackTargetType
+ private int mPreviousNavigationType;
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -180,7 +190,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
this(
shellInit,
shellController,
@@ -190,7 +201,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
context,
context.getContentResolver(),
backAnimationBackground,
- shellBackAnimationRegistry);
+ shellBackAnimationRegistry,
+ shellCommandHandler);
}
@VisibleForTesting
@@ -203,12 +215,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
Context context,
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
+ mRequirePointerPilfer =
+ context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mAnimationBackground = backAnimationBackground;
@@ -219,6 +234,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
.build();
mShellBackAnimationRegistry = shellBackAnimationRegistry;
mLatencyTracker = LatencyTracker.getInstance(mContext);
+ mShellCommandHandler = shellCommandHandler;
}
private void onInit() {
@@ -227,12 +243,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
private void setupAnimationDeveloperSettingsObserver(
@NonNull ContentResolver contentResolver,
@NonNull @ShellBackgroundThread final Handler backgroundHandler) {
- if (predictiveBackSystemAnimations()) {
+ if (predictiveBackSystemAnims()) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+ "developer settings flag is ignored and no content observer registered");
return;
@@ -255,7 +272,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@ShellBackgroundThread
private void updateEnableAnimationFromFlags() {
- boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+ boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
@@ -392,10 +409,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void onPilferPointers() {
- mCurrentTracker.updateStartLocation();
+ mPointerPilfered = true;
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
- if (!shouldDispatchToAnimator()) {
+ if (!shouldDispatchToAnimator() && mActiveCallback != null) {
+ mCurrentTracker.updateStartLocation();
tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
@@ -543,9 +561,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void tryDispatchOnBackStarted(IOnBackInvokedCallback callback,
+ private void tryDispatchOnBackStarted(
+ IOnBackInvokedCallback callback,
+ BackMotionEvent backEvent) {
+ if (mOnBackStartDispatched
+ || callback == null
+ || (!mPointerPilfered && mRequirePointerPilfer)) {
+ return;
+ }
+ dispatchOnBackStarted(callback, backEvent);
+ }
+
+ private void dispatchOnBackStarted(
+ IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (callback == null || mOnBackStartDispatched) {
+ if (callback == null) {
return;
}
try {
@@ -850,9 +880,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = null;
mShouldStartOnNextMoveEvent = false;
mOnBackStartDispatched = false;
+ mPointerPilfered = false;
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
if (mBackNavigationInfo != null) {
+ mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
}
@@ -932,9 +964,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (apps.length >= 1) {
mCurrentTracker.updateStartLocation();
- tryDispatchOnBackStarted(
- mActiveCallback,
- mCurrentTracker.createStartEvent(apps[0]));
+ BackMotionEvent startEvent =
+ mCurrentTracker.createStartEvent(apps[0]);
+ dispatchOnBackStarted(mActiveCallback, startEvent);
}
// Dispatch the first progress after animation start for
@@ -957,7 +989,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.execute(
() -> {
if (!mShellBackAnimationRegistry.cancel(
- mBackNavigationInfo.getType())) {
+ mBackNavigationInfo != null
+ ? mBackNavigationInfo.getType()
+ : mPreviousNavigationType)) {
return;
}
if (!mBackGestureStarted) {
@@ -968,4 +1002,22 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
};
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
+
+ /**
+ * Description of current BackAnimationController state.
+ */
+ private void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "BackAnimationController state:");
+ pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get());
+ pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
+ pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
+ pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mPointerPilfered=" + mPointerPilfered);
+ pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer);
+ pw.println(prefix + " mCurrentTracker state:");
+ mCurrentTracker.dump(pw, prefix + " ");
+ pw.println(prefix + " mQueuedTracker state:");
+ mQueuedTracker.dump(pw, prefix + " ");
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 215a6cc99e58..d6f7c367f772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -18,9 +18,9 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -40,7 +40,6 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -51,6 +50,7 @@ import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -65,7 +65,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE;
private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
new FloatProperty<>("enter-alpha") {
@Override
@@ -91,7 +91,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
};
private static final float MIN_WINDOW_ALPHA = 0.01f;
- private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final float WINDOW_X_SHIFT_DP = 48;
private static final int SCALE_FACTOR = 100;
// TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
@@ -126,6 +126,8 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
+ private boolean mIsRightEdge;
+ private boolean mTriggerBack = false;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
@@ -197,6 +199,10 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+ if (leash == null || !leash.isValid()) {
+ return;
+ }
+
final float scale = targetRect.width() / mStartTaskRect.width();
mTransformMatrix.reset();
mTransformMatrix.setScale(scale, scale);
@@ -209,11 +215,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private void finishAnimation() {
if (mEnteringTarget != null) {
- mEnteringTarget.leash.release();
+ if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) {
+ mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
+ mEnteringTarget.leash.release();
+ }
mEnteringTarget = null;
}
if (mClosingTarget != null) {
- mClosingTarget.leash.release();
+ if (mClosingTarget.leash != null) {
+ mClosingTarget.leash.release();
+ }
mClosingTarget = null;
}
if (mBackground != null) {
@@ -241,14 +252,15 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
float progress = backEvent.getProgress();
- float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
- ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ float springProgress = (mTriggerBack
+ ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
: mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
mLeavingProgressSpring.animateToFinalPosition(springProgress);
mEnteringProgressSpring.animateToFinalPosition(springProgress);
@@ -256,7 +268,9 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
private void onGestureCommitted() {
- if (mEnteringTarget == null || mClosingTarget == null) {
+ if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null
+ || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid()
+ || !mClosingTarget.leash.isValid()) {
finishAnimation();
return;
}
@@ -271,7 +285,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
ValueAnimator valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.setInterpolator(INTERPOLATOR);
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
@@ -296,12 +310,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, mEnteringProgress, 1.0f);
-
+ float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f);
mEnteringRect.set(left, top, left + width, top + height);
applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
}
+ private float getPreCommitEnteringAlpha() {
+ return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getEnteringProgress() {
return mEnteringProgress * SCALE_FACTOR;
}
@@ -311,9 +329,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
if (mEnteringTarget != null && mEnteringTarget.leash != null) {
transformWithProgress(
mEnteringProgress,
- Math.max(
- smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
- MIN_WINDOW_ALPHA), /* alpha */
+ getPreCommitEnteringAlpha(),
mEnteringTarget.leash,
mEnteringRect,
-mWindowXShift,
@@ -322,6 +338,11 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
}
+ private float getPreCommitLeavingAlpha() {
+ return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getLeavingProgress() {
return mLeavingProgress * SCALE_FACTOR;
}
@@ -331,20 +352,17 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
if (mClosingTarget != null && mClosingTarget.leash != null) {
transformWithProgress(
mLeavingProgress,
- Math.max(
- 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
- MIN_WINDOW_ALPHA),
+ getPreCommitLeavingAlpha(),
mClosingTarget.leash,
mClosingRect,
0,
- mWindowXShift
+ mIsRightEdge ? 0 : mWindowXShift
);
}
}
private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
RectF targetRect, float deltaXMin, float deltaXMax) {
- final float touchY = mTouchPos.y;
final int width = mStartTaskRect.width();
final int height = mStartTaskRect.height();
@@ -376,12 +394,14 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackStarted(backEvent,
CrossActivityBackAnimation.this::onGestureProgress);
}
@Override
public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 80fc3a867d48..4b3154190910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -91,7 +91,8 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final PointF mInitialTouchPos = new PointF();
private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
- private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
+ private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -136,6 +137,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
mStartTaskRect.offsetTo(0, 0);
+ // inset bottom in case of pinned taskbar being present
+ mStartTaskRect.inset(0, 0, 0, mClosingTarget.contentInsets.bottom);
+
// Draw background.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
BACKGROUNDCOLOR, mTransaction);
@@ -166,7 +170,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(height / 2f, Math.abs(rawYDelta)) / (height / 2f);
- float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+ float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f,
(height - scaledHeight) / 2f - mVerticalMargin);
@@ -205,7 +209,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float top = mapRange(progress, mClosingStartRect.top, targetTop);
float width = mapRange(progress, mClosingStartRect.width(), targetWidth);
float height = mapRange(progress, mClosingStartRect.height(), targetHeight);
- mTransaction.setLayer(mClosingTarget.leash, 0);
+ if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ }
mClosingCurrentRect.set(left, top, left + width, top + height);
applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
@@ -223,7 +229,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
/** Transform the target window to match the target rect. */
private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
- if (leash == null) {
+ if (leash == null || !leash.isValid()) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 4bd56d460818..8f04f126960c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -24,6 +24,8 @@ import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
import android.window.BackMotionEvent;
+import java.io.PrintWriter;
+
/**
* Helper class to record the touch location for gesture and generate back events.
*/
@@ -61,6 +63,10 @@ class TouchTracker {
if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT)
|| (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
mStartThresholdX = touchX;
+ if ((mSwipeEdge == BackEvent.EDGE_LEFT && mStartThresholdX < mInitTouchX)
+ || (mSwipeEdge == BackEvent.EDGE_RIGHT && mStartThresholdX > mInitTouchX)) {
+ mInitTouchX = mStartThresholdX;
+ }
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
@@ -129,6 +135,7 @@ class TouchTracker {
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ target);
}
@@ -204,6 +211,7 @@ class TouchTracker {
/* progress = */ progress,
/* velocityX = */ mLatestVelocityX,
/* velocityY = */ mLatestVelocityY,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ null);
}
@@ -219,6 +227,12 @@ class TouchTracker {
mNonLinearFactor = nonLinearFactor;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "TouchTracker state:");
+ pw.println(prefix + " mState=" + mState);
+ pw.println(prefix + " mTriggerBack=" + mTriggerBack);
+ }
+
enum TouchTrackerState {
INITIAL, ACTIVE, FINISHED
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 7a3210e0a46d..da530d740d48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -19,6 +19,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.AsyncTask.Status.FINISHED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.DimenRes;
import android.annotation.Hide;
@@ -47,6 +48,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -64,7 +66,7 @@ public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
/** A string suffix used in app bubbles' {@link #mKey}. */
- private static final String KEY_APP_BUBBLE = "key_app_bubble";
+ public static final String KEY_APP_BUBBLE = "key_app_bubble";
/** Whether the bubble is an app bubble. */
private final boolean mIsAppBubble;
@@ -105,6 +107,8 @@ public class Bubble implements BubbleViewProvider {
private BubbleExpandedView mExpandedView;
@Nullable
private BubbleBarExpandedView mBubbleBarExpandedView;
+ @Nullable
+ private BubbleTaskView mBubbleTaskView;
private BubbleViewInfoTask mInflationTask;
private boolean mInflateSynchronously;
@@ -394,6 +398,17 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new
+ * instance of {@link BubbleTaskView} is created.
+ */
+ public BubbleTaskView getOrCreateBubbleTaskView(BubbleTaskViewFactory taskViewFactory) {
+ if (mBubbleTaskView == null) {
+ mBubbleTaskView = taskViewFactory.create();
+ }
+ return mBubbleTaskView;
+ }
+
+ /**
* @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise.
*/
String getShortcutId() {
@@ -415,6 +430,10 @@ public class Bubble implements BubbleViewProvider {
* the bubble.
*/
void cleanupExpandedView() {
+ cleanupExpandedView(true);
+ }
+
+ private void cleanupExpandedView(boolean cleanupTaskView) {
if (mExpandedView != null) {
mExpandedView.cleanUpExpandedState();
mExpandedView = null;
@@ -423,17 +442,38 @@ public class Bubble implements BubbleViewProvider {
mBubbleBarExpandedView.cleanUpExpandedState();
mBubbleBarExpandedView = null;
}
+ if (cleanupTaskView) {
+ cleanupTaskView();
+ }
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
}
mIntentActive = false;
}
+ private void cleanupTaskView() {
+ if (mBubbleTaskView != null) {
+ mBubbleTaskView.cleanup();
+ mBubbleTaskView = null;
+ }
+ }
+
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
- cleanupExpandedView();
+ public void cleanupViews() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey());
+ cleanupViews(true);
+ }
+
+ /**
+ * Call when all the views should be removed/cleaned up.
+ *
+ * <p>If we're switching between bar and floating modes, pass {@code false} on
+ * {@code cleanupTaskView} to avoid recreating it in the new mode.
+ */
+ void cleanupViews(boolean cleanupTaskView) {
+ cleanupExpandedView(cleanupTaskView);
mIconView = null;
}
@@ -468,14 +508,18 @@ public class Bubble implements BubbleViewProvider {
*
* @param callback the callback to notify one the bubble is ready to be displayed.
* @param context the context for the bubble.
- * @param controller the bubble controller.
+ * @param expandedViewManager the bubble expanded view manager.
+ * @param taskViewFactory the task view factory used to create the task view for the bubble.
+ * @param positioner the bubble positioner.
* @param stackView the view the bubble is added to, iff showing as floating.
* @param layerView the layer the bubble is added to, iff showing in the bubble bar.
- * @param iconFactory the icon factory use to create images for the bubble.
+ * @param iconFactory the icon factory used to create images for the bubble.
*/
void inflate(BubbleViewInfoTask.Callback callback,
Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
@Nullable BubbleStackView stackView,
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
@@ -485,7 +529,9 @@ public class Bubble implements BubbleViewProvider {
}
mInflationTask = new BubbleViewInfoTask(this,
context,
- controller,
+ expandedViewManager,
+ taskViewFactory,
+ positioner,
stackView,
layerView,
iconFactory,
@@ -866,9 +912,8 @@ public class Bubble implements BubbleViewProvider {
if (uid != -1) {
intent.putExtra(Settings.EXTRA_APP_UID, uid);
}
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
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 249f52bd6156..96aaf02cb5e3 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
@@ -23,7 +23,6 @@ import static android.view.View.INVISIBLE;
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.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -114,6 +113,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -122,6 +122,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -137,7 +138,7 @@ import java.util.function.IntConsumer;
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
public class BubbleController implements ConfigurationChangeListener,
- RemoteCallable<BubbleController> {
+ RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -173,7 +174,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -191,18 +192,20 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellCommandHandler mShellCommandHandler;
private final IWindowManager mWmService;
private final BubbleProperties mBubbleProperties;
+ private final BubbleTaskViewFactory mBubbleTaskViewFactory;
+ private final BubbleExpandedViewManager mExpandedViewManager;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final ShellExecutor mBackgroundExecutor;
- private BubbleLogger mLogger;
- private BubbleData mBubbleData;
+ private final BubbleLogger mLogger;
+ private final BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubblePositioner mBubblePositioner;
+ private final BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
// Tracks the id of the current (foreground) user.
@@ -232,15 +235,22 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+ /**
+ * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+ */
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
+ /**
+ * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+ */
+ private final Rect mScreenBounds = new Rect();
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
+ /** Saved locale, used to detect local changes in {@link #onConfigurationChanged}. */
+ private Locale mLocale = null;
+
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
@@ -253,9 +263,9 @@ public class BubbleController implements ConfigurationChangeListener,
private boolean mIsStatusBarShade = true;
/** One handed mode controller to register transition listener. */
- private Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -281,7 +291,7 @@ public class BubbleController implements ConfigurationChangeListener,
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -330,6 +340,16 @@ public class BubbleController implements ConfigurationChangeListener,
mWmService = wmService;
mBubbleProperties = bubbleProperties;
shellInit.addInitCallback(this::onInit, this);
+ mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
+ @Override
+ public BubbleTaskView create() {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
+ context, organizer, taskViewTransitions, syncQueue);
+ TaskView taskView = new TaskView(context, taskViewTaskController);
+ return new BubbleTaskView(taskView, mainExecutor);
+ }
+ };
+ mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -431,6 +451,9 @@ public class BubbleController implements ConfigurationChangeListener,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
+ task.taskId, b.getKey());
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -438,6 +461,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
+ + "selecting matching overflow bubble=%s",
+ task.taskId, b.getKey());
promoteBubbleFromOverflow(b);
mBubbleData.setExpanded(true);
return;
@@ -459,7 +485,12 @@ public class BubbleController implements ConfigurationChangeListener,
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
- mDragAndDropController.ifPresent(controller -> controller.addListener(this::collapseStack));
+ mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() {
+ @Override
+ public void onDragStarted() {
+ collapseStack();
+ }
+ });
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
@@ -577,10 +608,15 @@ public class BubbleController implements ConfigurationChangeListener,
// Hide the stack temporarily if the status bar has been made invisible, and the stack
// is collapsed. An expanded stack should remain visible until collapsed.
mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
+ visible, isStackExpanded());
}
}
private void onZenStateChanged() {
+ if (hasBubbles()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
+ }
for (Bubble b : mBubbleData.getBubbles()) {
b.setShowDot(b.showInShade());
}
@@ -589,9 +625,10 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
boolean didChange = mIsStatusBarShade != isShade;
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
+ + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
+ isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
+ ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
mIsStatusBarShade = isShade;
if (!mIsStatusBarShade && didChange) {
// Only collapse stack on change
@@ -607,6 +644,8 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onBubbleMetadataFlagChanged(Bubble bubble) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
+ bubble.getKey(), bubble.getFlags());
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
@@ -619,6 +658,8 @@ public class BubbleController implements ConfigurationChangeListener,
/** Called when the current user changes. */
@VisibleForTesting
public void onUserChanged(int newUserId) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
+ mCurrentUserId, newUserId);
saveBubbles(mCurrentUserId);
mCurrentUserId = newUserId;
@@ -646,6 +687,17 @@ public class BubbleController implements ConfigurationChangeListener,
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ /** Called when sensitive notification state has changed */
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ if (mStackView != null) {
+ mStackView.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive);
+ ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b",
+ sensitiveNotificationProtectionActive);
+ }
+ }
+
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
return canShowAsBubbleBar() && mBubbleStateListener != null;
@@ -706,6 +758,7 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleIconFactory;
}
+ @Override
public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
@@ -725,15 +778,16 @@ public class BubbleController implements ConfigurationChangeListener,
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
if (mStackView == null) {
+ BubbleStackViewManager bubbleStackViewManager =
+ BubbleStackViewManager.fromBubbleController(this);
mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator,
- mMainExecutor);
+ mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+ mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -781,7 +835,13 @@ public class BubbleController implements ConfigurationChangeListener,
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
- mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
+ if (isShowingAsBubbleBar()) {
+ mBubbleData.getOverflow().initializeForBubbleBar(
+ mExpandedViewManager, mBubblePositioner);
+ } else {
+ mBubbleData.getOverflow().initialize(
+ mExpandedViewManager, mStackView, mBubblePositioner);
+ }
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
mWindowManager.addView(mLayerView, mWmLayoutParams);
@@ -819,6 +879,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
if (mAddedToWindowManager) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -893,11 +954,13 @@ public class BubbleController implements ConfigurationChangeListener,
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- @VisibleForTesting
public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
+ } else if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
}
}
@@ -960,7 +1023,9 @@ public class BubbleController implements ConfigurationChangeListener,
for (Bubble b : mBubbleData.getBubbles()) {
b.inflate(null /* callback */,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -969,7 +1034,9 @@ public class BubbleController implements ConfigurationChangeListener,
for (Bubble b : mBubbleData.getOverflowBubbles()) {
b.inflate(null /* callback */,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1005,12 +1072,18 @@ public class BubbleController implements ConfigurationChangeListener,
mLayoutDirection = newConfig.getLayoutDirection();
mStackView.onLayoutDirectionChanged(mLayoutDirection);
}
+ Locale newLocale = newConfig.locale;
+ if (newLocale != null && !newLocale.equals(mLocale)) {
+ mLocale = newLocale;
+ mStackView.updateLocale();
+ }
}
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onNotificationPanelExpandedChanged expanded=%b", expanded);
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
} else {
@@ -1047,7 +1120,6 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
- @VisibleForTesting
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
@@ -1092,6 +1164,7 @@ public class BubbleController implements ConfigurationChangeListener,
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
bubble.markAsAccessedAt(System.currentTimeMillis());
@@ -1105,9 +1178,8 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
- int bubbleBarOffsetY) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+ public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1208,11 +1280,8 @@ public class BubbleController implements ConfigurationChangeListener,
// Skip update, but store it in user bubbles so it gets restored after user switch
mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
true /* shownInShade */);
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG,
- "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
- + " current userId=" + mCurrentUserId);
- }
+ Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
+ + " currentUser=" + mCurrentUserId);
}
}
@@ -1249,6 +1318,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= "
+ + (mStackView != null ? mStackView.getVisibility() : " null ")
+ + " statusBarShade=" + mIsStatusBarShade);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
@@ -1258,19 +1330,31 @@ public class BubbleController implements ConfigurationChangeListener,
if (isStackExpanded()) {
if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
// App bubble is expanded, lets collapse
+ Log.i(TAG, " showOrHideAppBubble, selected bubble is app bubble, collapsing");
collapseStack();
} else {
// App bubble is not selected, select it
+ Log.i(TAG, " showOrHideAppBubble, expanded, selecting existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
}
} else {
// App bubble is not selected, select it & expand
+ Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
mBubbleData.setExpanded(true);
}
} else {
- // App bubble does not exist, lets add and expand it
- Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ // Check if it exists in the overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ if (b != null) {
+ // It's in the overflow, so remove it & reinflate
+ Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow");
+ mBubbleData.removeOverflowBubble(b);
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
+ b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ }
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1341,7 +1425,9 @@ public class BubbleController implements ConfigurationChangeListener,
bubble.inflate(
(b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1361,14 +1447,22 @@ public class BubbleController implements ConfigurationChangeListener,
mStackView.resetOverflowView();
mStackView.removeAllViews();
}
- // cleanup existing bubble views so they can be recreated later if needed.
- mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+ // cleanup existing bubble views so they can be recreated later if needed, but retain
+ // TaskView.
+ mBubbleData.getBubbles().forEach(b -> b.cleanupViews(/* cleanupTaskView= */ false));
// remove the current bubble container from window manager, null it out, and create a new
// container based on the current mode.
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
+
+ if (!mBubbleData.hasBubbles()) {
+ // if there are no bubbles, don't create the stack or layer views. they will be created
+ // later when the first bubble is added.
+ return;
+ }
+
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
@@ -1387,7 +1481,9 @@ public class BubbleController implements ConfigurationChangeListener,
Bubble bubble = mBubbleData.getBubbles().get(i);
bubble.inflate(callback,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1462,8 +1558,14 @@ public class BubbleController implements ConfigurationChangeListener,
// Lazy init stack view when a bubble is created
ensureBubbleViewsAndWindowCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
- bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
- mContext, this, mStackView, mLayerView,
+ bubble.inflate(
+ b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ mContext,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
+ mStackView,
+ mLayerView,
mBubbleIconFactory,
false /* skipInflation */);
}
@@ -1473,7 +1575,6 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>
* Must be called from the main thread.
*/
- @VisibleForTesting
@MainThread
public void removeBubble(String key, int reason) {
if (mBubbleData.hasAnyBubbleWithKey(key)) {
@@ -1698,8 +1799,12 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble, () -> {
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
+ });
}
}
@@ -1754,18 +1859,19 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void applyUpdate(BubbleData.Update update) {
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
- + " bubbleRemoved="
- + (update.removedBubbles != null && update.removedBubbles.size() > 0)
- + " bubbleUpdated=" + (update.updatedBubble != null)
- + " orderChanged=" + update.orderChanged
- + " expandedChanged=" + update.expandedChanged
- + " selectionChanged=" + update.selectionChanged
- + " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null)
- + " shouldShowEducation=" + update.shouldShowEducation);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ + " expanded=%b selectionChanged=%b selected=%s"
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+ update.addedBubble != null ? update.addedBubble.getKey() : "null",
+ !update.removedBubbles.isEmpty(),
+ update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
+ update.orderChanged, update.expandedChanged, update.expanded,
+ update.selectionChanged,
+ update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
+ update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
+ update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
+ update.shouldShowEducation);
ensureBubbleViewsAndWindowCreated();
@@ -1959,7 +2065,8 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView == null && mLayerView == null) {
return;
}
-
+ ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
+ mIsStatusBarShade, hasBubbles());
if (!mIsStatusBarShade) {
// Bubbles don't appear when the device is locked.
if (mStackView != null) {
@@ -2163,10 +2270,10 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarOffsetX, bubbleBarOffsetY));
+ key, bubbleBarBounds));
}
@Override
@@ -2497,6 +2604,14 @@ public class BubbleController implements ConfigurationChangeListener,
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
+
+ @Override
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive));
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index bbb4b74c2a17..6c2f925119f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,11 +15,10 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
@@ -36,8 +35,8 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
@@ -179,7 +178,7 @@ public class BubbleData {
* This interface reports changes to the state and appearance of bubbles which should be applied
* as necessary to the UI.
*/
- interface Listener {
+ public interface Listener {
/** Reports changes have have occurred as a result of the most recent operation. */
void applyUpdate(Update update);
}
@@ -332,9 +331,6 @@ public class BubbleData {
}
public void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
setExpandedInternal(expanded);
dispatchPendingChanges();
}
@@ -346,9 +342,8 @@ public class BubbleData {
* updated to have the correct state.
*/
public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s",
+ (bubble != null ? bubble.getKey() : "null"));
mExpanded = true;
if (Objects.equals(bubble, mSelectedBubble)) {
return;
@@ -369,9 +364,6 @@ public class BubbleData {
}
public void setSelectedBubble(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
@@ -425,16 +417,19 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
- * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
+ * BubbleTaskViewFactory, BubblePositioner, BubbleStackView,
+ * com.android.wm.shell.bubbles.bar.BubbleBarLayerView,
+ * com.android.launcher3.icons.BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + bubble);
- }
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.isTextChanged();
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b",
+ bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade,
+ bubble.shouldAutoExpand());
if (prevBubble == null) {
// Create a new bubble
@@ -482,14 +477,24 @@ public class BubbleData {
* Dismisses the bubble with the matching key, if it exists.
*/
public void dismissBubbleWithKey(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
- }
doRemove(key, reason);
dispatchPendingChanges();
}
/**
+ * Explicitly removes a bubble from the overflow, if it exists.
+ *
+ * @param bubble the bubble to remove.
+ */
+ public void removeOverflowBubble(Bubble bubble) {
+ if (bubble == null) return;
+ if (mOverflowBubbles.remove(bubble)) {
+ mStateChange.removedOverflowBubble = bubble;
+ dispatchPendingChanges();
+ }
+ }
+
+ /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -589,9 +594,7 @@ public class BubbleData {
}
private void doAdd(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doAdd: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey());
mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
// Adding the first bubble doesn't change the order
@@ -620,9 +623,7 @@ public class BubbleData {
}
private void doUpdate(Bubble bubble, boolean reorder) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUpdate: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey());
mStateChange.updatedBubble = bubble;
if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
@@ -649,9 +650,6 @@ public class BubbleData {
}
private void doRemove(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doRemove: " + key);
- }
// If it was pending remove it
if (mPendingBubbles.containsKey(key)) {
mPendingBubbles.remove(key);
@@ -672,9 +670,7 @@ public class BubbleData {
&& shouldRemoveHiddenBubble) {
Bubble b = getOverflowBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel overflow bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
if (b != null) {
b.stopInflation();
}
@@ -685,9 +681,7 @@ public class BubbleData {
}
if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
Bubble b = getSuppressedBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel suppressed bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key);
if (b != null) {
mSuppressedBubbles.remove(b.getLocusId());
b.stopInflation();
@@ -697,6 +691,7 @@ public class BubbleData {
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
bubbleToRemove.stopInflation();
overflowBubble(reason, bubbleToRemove);
@@ -730,17 +725,12 @@ public class BubbleData {
}
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
- }
BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
private void doSuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doSuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey());
mStateChange.suppressedBubble = bubble;
bubble.setSuppressBubble(true);
@@ -763,9 +753,7 @@ public class BubbleData {
}
private void doUnsuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUnsuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey());
bubble.setSuppressBubble(false);
mStateChange.unsuppressedBubble = bubble;
mBubbles.add(bubble);
@@ -787,9 +775,7 @@ public class BubbleData {
|| reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflowing: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
mLogger.logOverflowAdd(bubble, reason);
mOverflowBubbles.remove(bubble);
mOverflowBubbles.add(0, bubble);
@@ -798,9 +784,7 @@ public class BubbleData {
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + oldest);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey());
mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED);
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
mOverflowBubbles.remove(oldest);
@@ -809,9 +793,7 @@ public class BubbleData {
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "dismissAll: reason=" + reason);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason);
if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
return;
}
@@ -837,9 +819,10 @@ public class BubbleData {
* @param visible whether the task with the locusId is visible or not.
*/
public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
- }
+ if (locusId == null) return;
+
+ ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d",
+ locusId.getId(), visible, taskId);
Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
// Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
@@ -896,9 +879,8 @@ public class BubbleData {
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s",
+ (bubble != null ? bubble.getKey() : "null"));
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
@@ -955,12 +937,10 @@ public class BubbleData {
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
- }
if (mExpanded == shouldExpand) {
return;
}
+ ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand);
if (shouldExpand) {
if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
@@ -1012,9 +992,6 @@ public class BubbleData {
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "repackAll()");
- }
if (mBubbles.isEmpty()) {
return false;
}
@@ -1055,7 +1032,6 @@ public class BubbleData {
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
@@ -1144,7 +1120,6 @@ public class BubbleData {
return null;
}
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
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 f56b1712c5c1..f1a68e246fe1 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
@@ -37,17 +37,7 @@ public class BubbleDebugConfig {
// Default log tag for the Bubbles package.
public static final String TAG_BUBBLES = "Bubbles";
-
- static final boolean DEBUG_BUBBLE_CONTROLLER = false;
- static final boolean DEBUG_BUBBLE_DATA = false;
- static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
- static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
- static final boolean DEBUG_EXPERIMENTS = true;
- static final boolean DEBUG_OVERFLOW = false;
public static final boolean DEBUG_USER_EDUCATION = false;
- static final boolean DEBUG_POSITIONER = false;
- public static final boolean DEBUG_COLLAPSE_ANIMATOR = 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/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
index e57f02c71e44..bd4708259b50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
@@ -40,7 +40,13 @@ class BubbleEducationController(private val context: Context) {
/** Whether education view should show for the collapsed stack. */
fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean {
- val shouldShow = bubble != null &&
+ if (BubbleDebugConfig.neverShowUserEducation(context)) {
+ logDebug("Show stack edu: never")
+ return false
+ }
+
+ val shouldShow =
+ bubble != null &&
bubble.isConversationBubble && // show education for conversation bubbles only
(!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context))
logDebug("Show stack edu: $shouldShow")
@@ -49,7 +55,13 @@ class BubbleEducationController(private val context: Context) {
/** Whether the educational view should show for the expanded view "manage" menu. */
fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean {
- val shouldShow = bubble != null &&
+ if (BubbleDebugConfig.neverShowUserEducation(context)) {
+ logDebug("Show manage edu: never")
+ return false
+ }
+
+ val shouldShow =
+ bubble != null &&
bubble.isConversationBubble && // show education for conversation bubbles only
(!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context))
logDebug("Show manage edu: $shouldShow")
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 a3eb429b1d7e..74f087b6d8f8 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
@@ -23,16 +23,14 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -49,7 +47,6 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
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;
@@ -70,11 +67,11 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
@@ -146,7 +143,6 @@ public class BubbleExpandedView extends LinearLayout {
private AlphaOptimizedButton mManageButton;
private TaskView mTaskView;
- private TaskViewTaskController mTaskViewTaskController;
private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -180,7 +176,9 @@ public class BubbleExpandedView extends LinearLayout {
private float mCornerRadius = 0f;
private int mBackgroundColorFloating;
private boolean mUsingMaxHeight;
+ private int mLeftClip = 0;
private int mTopClip = 0;
+ private int mRightClip = 0;
private int mBottomClip = 0;
@Nullable private Bubble mBubble;
private PendingIntent mPendingIntent;
@@ -188,7 +186,7 @@ public class BubbleExpandedView extends LinearLayout {
private boolean mIsOverflow;
private boolean mIsClipping;
- private BubbleController mController;
+ private BubbleExpandedViewManager mManager;
private BubbleStackView mStackView;
private BubblePositioner mPositioner;
@@ -204,13 +202,9 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
-
if (mDestroyed || mInitialized) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
return;
}
@@ -221,10 +215,8 @@ public class BubbleExpandedView extends LinearLayout {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
Rect launchBounds = new Rect();
mTaskView.getBoundsOnScreen(launchBounds);
@@ -271,7 +263,7 @@ public class BubbleExpandedView extends LinearLayout {
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ mManager.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
}
});
mInitialized = true;
@@ -284,16 +276,14 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
if (mBubble != null && mBubble.isAppBubble()) {
// Let the controller know sooner what the taskId is.
- mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+ mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
}
// With the task org, the taskAppeared callback will only happen once the task has
@@ -303,17 +293,17 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d",
+ visible, getBubbleKey(), taskId);
setContentVisibility(visible);
}
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
- mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
if (mTaskView != null) {
// Release the surface
@@ -365,7 +355,8 @@ public class BubbleExpandedView extends LinearLayout {
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
+ Rect clip = new Rect(mLeftClip, mTopClip, view.getWidth() - mRightClip,
+ view.getHeight() - mBottomClip);
outline.setRoundRect(clip, mCornerRadius);
}
});
@@ -433,16 +424,20 @@ public class BubbleExpandedView extends LinearLayout {
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
*/
- void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
- mController = controller;
+ void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubbleStackView stackView,
+ BubblePositioner positioner,
+ boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
+ mManager = expandedViewManager;
mStackView = stackView;
mIsOverflow = isOverflow;
- mPositioner = mController.getPositioner();
+ mPositioner = positioner;
if (mIsOverflow) {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_overflow_container, null /* root */);
- mOverflowView.setBubbleController(mController);
+ mOverflowView.initialize(expandedViewManager, positioner);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
mExpandedViewContainer.addView(mOverflowView, lp);
mExpandedViewContainer.setLayoutParams(
@@ -450,18 +445,22 @@ public class BubbleExpandedView extends LinearLayout {
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
// set a fixed width so it is not recalculated as part of a rotation. the width will be
// updated manually after the rotation.
FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
mExpandedViewContainer.addView(mTaskView, lp);
bringChildToFront(mTaskView);
+ if (bubbleTaskView.isCreated()) {
+ mTaskViewListener.onTaskCreated(
+ bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+ }
}
}
@@ -518,6 +517,15 @@ public class BubbleExpandedView extends LinearLayout {
}
}
+ void updateLocale() {
+ if (mManageButton != null) {
+ mManageButton.setText(mContext.getString(R.string.manage_bubbles_text));
+ }
+ if (mOverflowView != null) {
+ mOverflowView.updateLocale();
+ }
+ }
+
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
@@ -644,9 +652,6 @@ public class BubbleExpandedView extends LinearLayout {
super.onDetachedFromWindow();
mImeVisible = false;
mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
}
/**
@@ -763,8 +768,26 @@ public class BubbleExpandedView extends LinearLayout {
onContainerClipUpdate();
}
+ /**
+ * Sets the clipping for the view.
+ */
+ public void setTaskViewClip(Rect rect) {
+ mLeftClip = rect.left;
+ mTopClip = rect.top;
+ mRightClip = rect.right;
+ mBottomClip = rect.bottom;
+ onContainerClipUpdate();
+ }
+
+ /**
+ * Returns a rect representing the clipping for the view.
+ */
+ public Rect getTaskViewClip() {
+ return new Rect(mLeftClip, mTopClip, mRightClip, mBottom);
+ }
+
private void onContainerClipUpdate() {
- if (mTopClip == 0 && mBottomClip == 0) {
+ if (mTopClip == 0 && mBottomClip == 0 && mRightClip == 0 && mLeftClip == 0) {
if (mIsClipping) {
mIsClipping = false;
if (mTaskView != null) {
@@ -782,8 +805,10 @@ public class BubbleExpandedView extends LinearLayout {
}
mExpandedViewContainer.invalidateOutline();
if (mTaskView != null) {
- mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
- mTaskView.getHeight() - mBottomClip));
+ Rect clipBounds = new Rect(mLeftClip, mTopClip,
+ mTaskView.getWidth() - mRightClip,
+ mTaskView.getHeight() - mBottomClip);
+ mTaskView.setClipBounds(clipBounds);
}
}
}
@@ -805,10 +830,6 @@ public class BubbleExpandedView extends LinearLayout {
* and setting {@code false} actually means rendering the contents in transparent.
*/
public void setContentVisibility(boolean visibility) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setContentVisibility: visibility=" + visibility
- + " bubble=" + getBubbleKey());
- }
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
@@ -867,15 +888,12 @@ public class BubbleExpandedView extends LinearLayout {
* Sets the bubble used to populate this view.
*/
void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + bubble);
- }
if (mStackView == null) {
Log.w(TAG, "Stack is null for bubble: " + bubble);
return;
}
boolean isNew = mBubble == null || didBackingContentChange(bubble);
- if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
mBubble = bubble;
mManageButton.setContentDescription(getResources().getString(
R.string.bubbles_settings_button_description, bubble.getAppName()));
@@ -958,11 +976,6 @@ public class BubbleExpandedView extends LinearLayout {
}
mNeedsNewHeight = false;
}
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
}
}
@@ -974,10 +987,6 @@ public class BubbleExpandedView extends LinearLayout {
* waiting for layout.
*/
public void updateView(int[] containerLocationOnScreen) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateView: bubble="
- + getBubbleKey());
- }
mExpandedViewContainerLocation = containerLocationOnScreen;
updateHeight();
if (mTaskView != null
@@ -1101,31 +1110,8 @@ public class BubbleExpandedView extends LinearLayout {
return ((LinearLayout.LayoutParams) mManageButton.getLayoutParams()).getMarginStart();
}
- /**
- * Cleans up anything related to the task. The TaskView itself is released after the task
- * has been removed.
- *
- * If this view should be reused after this method is called, then
- * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
- */
+ /** Hide the task view. */
public void cleanUpExpandedState() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (getTaskId() != INVALID_TASK_ID) {
- // Ensure the task is removed from WM
- if (ENABLE_SHELL_TRANSITIONS) {
- if (mTaskView != null) {
- mTaskView.removeTask();
- }
- } else {
- try {
- ActivityTaskManager.getService().removeTask(getTaskId());
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- }
if (mTaskView != null) {
mTaskView.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
new file mode 100644
index 000000000000..b0d3cc4a5d5c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Manager interface for bubble expanded views. */
+interface BubbleExpandedViewManager {
+
+ val overflowBubbles: List<Bubble>
+ fun setOverflowListener(listener: BubbleData.Listener)
+ fun collapseStack()
+ fun updateWindowFlagsForBackpress(intercept: Boolean)
+ fun promoteBubbleFromOverflow(bubble: Bubble)
+ fun removeBubble(key: String, reason: Int)
+ fun dismissBubble(bubble: Bubble, reason: Int)
+ fun setAppBubbleTaskId(key: String, taskId: Int)
+ fun isStackExpanded(): Boolean
+ fun isShowingAsBubbleBar(): Boolean
+
+ companion object {
+ /**
+ * Convenience function for creating a [BubbleExpandedViewManager] that delegates to the
+ * given `controller`.
+ */
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController): BubbleExpandedViewManager {
+ return object : BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = controller.overflowBubbles
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {
+ controller.setOverflowListener(listener)
+ }
+
+ override fun collapseStack() {
+ controller.collapseStack()
+ }
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {
+ controller.updateWindowFlagsForBackpress(intercept)
+ }
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {
+ controller.promoteBubbleFromOverflow(bubble)
+ }
+
+ override fun removeBubble(key: String, reason: Int) {
+ controller.removeBubble(key, reason)
+ }
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {
+ controller.dismissBubble(bubble, reason)
+ }
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {
+ controller.setAppBubbleTaskId(key, taskId)
+ }
+
+ override fun isStackExpanded(): Boolean = controller.isStackExpanded
+
+ override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 22e836aacfc5..f32974e1765d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,6 +29,7 @@ import android.util.PathParser
import android.view.LayoutInflater
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -55,13 +56,32 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
}
/** Call before use and again if cleanUpExpandedState was called. */
- fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
- if (forBubbleBar) {
- createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
- } else {
- createExpandedView()
- .initialize(controller, controller.stackView, true /* isOverflow */)
- }
+ fun initialize(
+ expandedViewManager: BubbleExpandedViewManager,
+ stackView: BubbleStackView,
+ positioner: BubblePositioner
+ ) {
+ createExpandedView()
+ .initialize(
+ expandedViewManager,
+ stackView,
+ positioner,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
+ }
+
+ fun initializeForBubbleBar(
+ expandedViewManager: BubbleExpandedViewManager,
+ positioner: BubblePositioner
+ ) {
+ createBubbleBarExpandedView()
+ .initialize(
+ expandedViewManager,
+ positioner,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
}
fun cleanUpExpandedState() {
@@ -113,7 +133,10 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(com.android.launcher3.icons.R.color.important_conversation),
+ ContextCompat.getColor(
+ context,
+ com.android.launcher3.icons.R.color.important_conversation
+ ),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
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 9655470ce914..633b01bde4ca 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
@@ -16,9 +16,9 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.content.Context;
@@ -28,7 +28,6 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -43,6 +42,7 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.R;
@@ -62,7 +62,8 @@ public class BubbleOverflowContainerView extends LinearLayout {
private ImageView mEmptyStateImage;
private int mHorizontalMargin;
private int mVerticalMargin;
- private BubbleController mController;
+ private BubbleExpandedViewManager mExpandedViewManager;
+ private BubblePositioner mPositioner;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -70,7 +71,7 @@ public class BubbleOverflowContainerView extends LinearLayout {
private View.OnKeyListener mKeyListener = (view, i, keyEvent) -> {
if (keyEvent.getAction() == KeyEvent.ACTION_UP
&& keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- mController.collapseStack();
+ mExpandedViewManager.collapseStack();
return true;
}
return false;
@@ -126,8 +127,11 @@ public class BubbleOverflowContainerView extends LinearLayout {
setFocusableInTouchMode(true);
}
- public void setBubbleController(BubbleController controller) {
- mController = controller;
+ /** Initializes the view. Must be called after creation. */
+ public void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubblePositioner positioner) {
+ mExpandedViewManager = expandedViewManager;
+ mPositioner = positioner;
}
public void show() {
@@ -149,9 +153,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mController != null) {
+ if (mExpandedViewManager != null) {
// For the overflow to get key events (e.g. back press) we need to adjust the flags
- mController.updateWindowFlagsForBackpress(true);
+ mExpandedViewManager.updateWindowFlagsForBackpress(true);
}
setOnKeyListener(mKeyListener);
}
@@ -159,8 +163,8 @@ public class BubbleOverflowContainerView extends LinearLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mController != null) {
- mController.updateWindowFlagsForBackpress(false);
+ if (mExpandedViewManager != null) {
+ mExpandedViewManager.updateWindowFlagsForBackpress(false);
}
setOnKeyListener(null);
}
@@ -177,15 +181,15 @@ public class BubbleOverflowContainerView extends LinearLayout {
mRecyclerView.addItemDecoration(new OverflowItemDecoration());
}
mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
- mController::promoteBubbleFromOverflow,
- mController.getPositioner());
+ mExpandedViewManager::promoteBubbleFromOverflow,
+ mPositioner);
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
- mOverflowBubbles.addAll(mController.getOverflowBubbles());
+ mOverflowBubbles.addAll(mExpandedViewManager.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
- mController.setOverflowListener(mDataListener);
+ mExpandedViewManager.setOverflowListener(mDataListener);
updateEmptyStateVisibility();
updateTheme();
}
@@ -238,6 +242,11 @@ public class BubbleOverflowContainerView extends LinearLayout {
mEmptyStateSubtitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
}
+ public void updateLocale() {
+ mEmptyStateTitle.setText(mContext.getString(R.string.bubble_overflow_empty_title));
+ mEmptyStateSubtitle.setText(mContext.getString(R.string.bubble_overflow_empty_subtitle));
+ }
+
private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
@Override
@@ -245,9 +254,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toRemove = update.removedOverflowBubble;
if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
toRemove.cleanupViews();
final int indexToRemove = mOverflowBubbles.indexOf(toRemove);
mOverflowBubbles.remove(toRemove);
@@ -257,9 +263,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toAdd = update.addedOverflowBubble;
if (toAdd != null) {
final int indexToAdd = mOverflowBubbles.indexOf(toAdd);
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd);
- }
if (indexToAdd > 0) {
mOverflowBubbles.remove(toAdd);
mOverflowBubbles.add(0, toAdd);
@@ -272,10 +275,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
updateEmptyStateVisibility();
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mController.getOverflowBubbles(), null));
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s",
+ (toAdd != null ? toAdd.getKey() : "null"),
+ (toRemove != null ? toRemove.getKey() : "null"));
}
};
}
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 662a5c47d633..a5853d621cb5 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,19 +16,20 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
@@ -37,9 +38,6 @@ import com.android.wm.shell.R;
* placement and positioning calculations to refer to.
*/
public class BubblePositioner {
- private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
- ? "BubblePositioner"
- : BubbleDebugConfig.TAG_BUBBLES;
/** The screen edge the bubble stack is pinned to */
public enum StackPinnedEdge {
@@ -95,10 +93,9 @@ public class BubblePositioner {
private int mMinimumFlyoutWidthLargeScreen;
private PointF mRestingStackPosition;
- private int[] mPaddings = new int[4];
private boolean mShowingInBubbleBar;
- private final Point mBubbleBarPosition = new Point();
+ private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -112,21 +109,24 @@ public class BubblePositioner {
*/
public void update(DeviceConfig deviceConfig) {
mDeviceConfig = deviceConfig;
-
- if (BubbleDebugConfig.DEBUG_POSITIONER) {
- Log.w(TAG, "update positioner:"
- + " rotation: " + mRotation
- + " insets: " + deviceConfig.getInsets()
- + " isLargeScreen: " + deviceConfig.isLargeScreen()
- + " isSmallTablet: " + deviceConfig.isSmallTablet()
- + " showingInBubbleBar: " + mShowingInBubbleBar
- + " bounds: " + deviceConfig.getWindowBounds());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: "
+ + "rotation=%d insets=%s largeScreen=%b "
+ + "smallTablet=%b isBubbleBar=%b bounds=%s",
+ mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(),
+ deviceConfig.isSmallTablet(), mShowingInBubbleBar,
+ deviceConfig.getWindowBounds());
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
+ BubbleStackView.RelativeStackPosition prevStackPosition = null;
+ if (mRestingStackPosition != null && mScreenRect != null && !mScreenRect.equals(bounds)) {
+ // Save the resting position as a relative position with the previous bounds, at the
+ // end of the update we'll restore it based on the new bounds.
+ prevStackPosition = new BubbleStackView.RelativeStackPosition(getRestingPosition(),
+ getAllowableStackPositionRegion(1));
+ }
mRotation = rotation;
mInsets = insets;
@@ -189,6 +189,12 @@ public class BubblePositioner {
R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
+
+ if (prevStackPosition != null) {
+ // Get the new resting position based on the updated values
+ mRestingStackPosition = prevStackPosition.getAbsolutePositionInRegion(
+ getAllowableStackPositionRegion(1));
+ }
}
/**
@@ -344,46 +350,44 @@ public class BubblePositioner {
final int pointerTotalHeight = getPointerSize();
final int expandedViewLargeScreenInsetFurthestEdge =
getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
+ int[] paddings = new int[4];
if (mDeviceConfig.isLargeScreen()) {
// Note:
// If we're in portrait OR if we're a small tablet, then the two insets values will
// be equal. If we're landscape and a large tablet, the two values will be different.
// [left, top, right, bottom]
- mPaddings[0] = onLeft
+ paddings[0] = onLeft
? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
: expandedViewLargeScreenInsetFurthestEdge;
- mPaddings[1] = 0;
- mPaddings[2] = onLeft
+ paddings[1] = 0;
+ paddings[2] = onLeft
? expandedViewLargeScreenInsetFurthestEdge
: mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
// Overflow doesn't show manage button / get padding from it so add padding here
- mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
- return mPaddings;
+ paddings[3] = isOverflow ? mExpandedViewPadding : 0;
+ return paddings;
} else {
int leftPadding = mInsets.left + mExpandedViewPadding;
int rightPadding = mInsets.right + mExpandedViewPadding;
- final float expandedViewWidth = isOverflow
- ? mOverflowWidth
- : mExpandedViewLargeScreenWidth;
if (showBubblesVertically()) {
if (!onLeft) {
rightPadding += mBubbleSize - pointerTotalHeight;
leftPadding += isOverflow
- ? (mPositionRect.width() - rightPadding - expandedViewWidth)
+ ? (mPositionRect.width() - rightPadding - mOverflowWidth)
: 0;
} else {
leftPadding += mBubbleSize - pointerTotalHeight;
rightPadding += isOverflow
- ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+ ? (mPositionRect.width() - leftPadding - mOverflowWidth)
: 0;
}
}
// [left, top, right, bottom]
- mPaddings[0] = leftPadding;
- mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
- mPaddings[2] = rightPadding;
- mPaddings[3] = 0;
- return mPaddings;
+ paddings[0] = leftPadding;
+ paddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+ paddings[2] = rightPadding;
+ paddings[3] = 0;
+ return paddings;
}
}
@@ -391,11 +395,11 @@ public class BubblePositioner {
public int getTaskViewContentWidth(boolean onLeft) {
int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false);
int pointerOffset = showBubblesVertically() ? getPointerSize() : 0;
- return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset;
+ return mScreenRect.width() - paddings[0] - paddings[2] - pointerOffset;
}
/** Gets the y position of the expanded view if it was top-aligned. */
- public float getExpandedViewYTopAligned() {
+ public int getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
if (showBubblesVertically()) {
return top - mPointerWidth + mExpandedViewPadding;
@@ -413,7 +417,7 @@ public class BubblePositioner {
return getExpandedViewHeightForLargeScreen();
}
// Subtract top insets because availableRect.height would account for that
- int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+ int expandedContainerY = getExpandedViewYTopAligned() - getInsets().top;
int paddingTop = showBubblesVertically()
? 0
: mPointerHeight;
@@ -474,11 +478,11 @@ public class BubblePositioner {
public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
- float topAlignment = getExpandedViewYTopAligned();
+ int topAlignment = getExpandedViewYTopAligned();
int manageButtonHeight =
isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
- // On largescreen portrait bubbles are bottom aligned.
+ // On large screen portrait bubbles are bottom aligned.
if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
return mPositionRect.bottom - manageButtonHeight
- getExpandedViewHeightForLargeScreen() - mPointerWidth;
@@ -794,15 +798,10 @@ public class BubblePositioner {
}
/**
- * Sets the position of the bubble bar in screen coordinates.
- *
- * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
- * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ * Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(int offsetX, int offsetY) {
- mBubbleBarPosition.set(
- getAvailableRect().width() - offsetX,
- getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
+ public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ mBubbleBarBounds.set(bubbleBarBounds);
}
/**
@@ -823,7 +822,7 @@ public class BubblePositioner {
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarPosition.y - mExpandedViewPadding;
+ return mBubbleBarBounds.top - mExpandedViewPadding;
}
/**
@@ -834,9 +833,9 @@ public class BubblePositioner {
}
/**
- * Returns the on screen co-ordinates of the bubble bar.
+ * Returns the display coordinates of the bubble bar.
*/
- public Point getBubbleBarPosition() {
- return mBubbleBarPosition;
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
}
}
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 b7f749e8a8b6..474430eb44ab 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,7 +21,6 @@ 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_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -204,8 +203,9 @@ public class BubbleStackView extends FrameLayout
Choreographer.getInstance().postFrameCallback(frameCallback);
}
};
- private final BubbleController mBubbleController;
+ private final BubbleStackViewManager mManager;
private final BubbleData mBubbleData;
+ private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -291,6 +291,11 @@ public class BubbleStackView extends FrameLayout
*/
private boolean mRemovingLastBubbleWhileExpanded = false;
+ /**
+ * Whether sensitive notification protection should disable flyout
+ */
+ private boolean mSensitiveNotificationProtectionActive = false;
+
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
@@ -300,6 +305,9 @@ public class BubbleStackView extends FrameLayout
*/
private int mPointerIndexDown = -1;
+ /** Indicates whether bubbles should be reordered at the end of a gesture. */
+ private boolean mShouldReorderBubblesAfterGestureCompletes = false;
+
@Nullable
private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
@@ -438,43 +446,42 @@ public class BubbleStackView extends FrameLayout
/** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */
private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener =
new MagnetizedObject.MagnetListener() {
+
@Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject draggedObject) {
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ animateDismissBubble(view, true);
}
- animateDismissBubble(
- mExpandedAnimationController.getDraggedOutBubble(), true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
- }
- animateDismissBubble(
- mExpandedAnimationController.getDraggedOutBubble(), false);
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ animateDismissBubble(view, false);
- if (wasFlungOut) {
- mExpandedAnimationController.snapBubbleBack(
- mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
- mDismissView.hide();
- } else {
- mExpandedAnimationController.onUnstuckFromTarget();
+ if (wasFlungOut) {
+ mExpandedAnimationController.snapBubbleBack(view, velX, velY);
+ mDismissView.hide();
+ } else {
+ mExpandedAnimationController.onUnstuckFromTarget();
+ }
}
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ mExpandedAnimationController.dismissDraggedOutBubble(
+ view /* bubble */,
+ mDismissView.getHeight() /* translationYBy */,
+ () -> dismissBubbleIfExists(
+ mBubbleData.getBubbleWithView(view)) /* after */);
}
- mExpandedAnimationController.dismissDraggedOutBubble(
- mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
- mDismissView.getHeight() /* translationYBy */,
- BubbleStackView.this::dismissMagnetizedObject /* after */);
mDismissView.hide();
}
};
@@ -484,12 +491,14 @@ public class BubbleStackView extends FrameLayout
new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(
- @NonNull MagnetizedObject.MagneticTarget target) {
+ @NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
animateDismissBubble(mBubbleContainer, true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
animateDismissBubble(mBubbleContainer, false);
if (wasFlungOut) {
@@ -502,14 +511,14 @@ public class BubbleStackView extends FrameLayout
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
mStackAnimationController.animateStackDismissal(
mDismissView.getHeight() /* translationYBy */,
() -> {
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
resetDismissAnimator();
- dismissMagnetizedObject();
- }
- );
+ } /*after */);
mDismissView.hide();
}
};
@@ -849,6 +858,7 @@ public class BubbleStackView extends FrameLayout
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
+ private StackEducationView.Manager mStackEducationViewManager;
private ManageEducationView mManageEduView;
private DismissView mDismissView;
@@ -864,15 +874,18 @@ public class BubbleStackView extends FrameLayout
private BubblePositioner mPositioner;
@SuppressLint("ClickableViewAccessibility")
- public BubbleStackView(Context context, BubbleController bubbleController,
- BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+ public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+ BubblePositioner bubblePositioner, BubbleData data,
+ @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
+ Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
- mBubbleController = bubbleController;
+ mManager = bubbleStackViewManager;
mBubbleData = data;
+ mSysuiProxyProvider = sysuiProxyProvider;
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
@@ -882,7 +895,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mPositioner = mBubbleController.getPositioner();
+ mPositioner = bubblePositioner;
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.dialogCornerRadius});
@@ -892,7 +905,7 @@ public class BubbleStackView extends FrameLayout
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
mExpandedViewTemporarilyHidden = false;
- mBubbleController.onAllBubblesAnimatedOut();
+ mManager.onAllBubblesAnimatedOut();
}
};
mStackAnimationController = new StackAnimationController(
@@ -1177,14 +1190,17 @@ public class BubbleStackView extends FrameLayout
if (mStackAnimationController.isStackOnLeftSide()) {
int availableRectOffsetX =
mPositioner.getAvailableRect().left - mPositioner.getScreenRect().left;
- animate().translationX(-(mBubbleSize + availableRectOffsetX)).start();
+ mBubbleContainer
+ .animate()
+ .translationX(-(mBubbleSize + availableRectOffsetX))
+ .start();
} else {
int availableRectOffsetX =
mPositioner.getAvailableRect().right - mPositioner.getScreenRect().right;
- animate().translationX(mBubbleSize - availableRectOffsetX).start();
+ mBubbleContainer.animate().translationX(mBubbleSize - availableRectOffsetX).start();
}
} else {
- animate().translationX(0).start();
+ mBubbleContainer.animate().translationX(0).start();
}
};
@@ -1291,16 +1307,12 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
+ final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show manage edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show manage edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show manage edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1342,15 +1354,11 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
+ final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show stack edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show stack edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show stack edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1369,7 +1377,9 @@ public class BubbleStackView extends FrameLayout
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
}
return showStackEdu();
@@ -1398,7 +1408,9 @@ public class BubbleStackView extends FrameLayout
private void updateUserEdu() {
if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
showStackEdu();
}
@@ -1435,6 +1447,12 @@ public class BubbleStackView extends FrameLayout
}
}
+ void updateLocale() {
+ if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+ mBubbleOverflow.getExpandedView().updateLocale();
+ }
+ }
+
private void updateOverflow() {
mBubbleOverflow.update();
mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
@@ -1493,7 +1511,7 @@ public class BubbleStackView extends FrameLayout
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
- Log.d(TAG, "Display size changed. Icon null: " + b);
+ Log.w(TAG, "Display size changed. Icon null: " + b);
continue;
}
b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
@@ -1799,10 +1817,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "addBubble: " + bubble);
- }
-
final boolean firstBubble = getBubbleCount() == 0;
if (firstBubble && shouldShowStackEdu()) {
@@ -1848,9 +1862,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "removeBubble: " + bubble);
- }
if (isExpanded() && getBubbleCount() == 1) {
mRemovingLastBubbleWhileExpanded = true;
// We're expanded while the last bubble is being removed. Let the scrim animate away
@@ -1897,7 +1908,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
}
@@ -1925,7 +1936,18 @@ public class BubbleStackView extends FrameLayout
/**
* Update bubble order and pointer position.
*/
- public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPositoion) {
+ public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPosition) {
+ // Don't reorder bubbles in the middle of a gesture because that would remove bubbles from
+ // view hierarchy and will cancel all touch events. Instead wait until the gesture is
+ // finished and then reorder.
+ if (mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = true;
+ return;
+ }
+ updateBubbleOrderInternal(bubbles, updatePointerPosition);
+ }
+
+ private void updateBubbleOrderInternal(List<Bubble> bubbles, boolean updatePointerPosition) {
final Runnable reorder = () -> {
for (int i = 0; i < bubbles.size(); i++) {
Bubble bubble = bubbles.get(i);
@@ -1936,13 +1958,13 @@ public class BubbleStackView extends FrameLayout
reorder.run();
updateBadges(false /* setBadgeForCollapsedStack */);
updateZOrder();
- } else if (!isExpansionAnimating()) {
+ } else {
List<View> bubbleViews = bubbles.stream()
.map(b -> b.getIconView()).collect(Collectors.toList());
mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- if (updatePointerPositoion) {
+ if (updatePointerPosition) {
updatePointerPosition(false /* forIme */);
}
}
@@ -1954,10 +1976,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
- }
-
if (bubbleToSelect == null) {
mBubbleData.setShowingOverflow(false);
return;
@@ -2050,10 +2068,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setExpanded: " + shouldExpand);
- }
-
if (!shouldExpand) {
// If we're collapsing, release the animating-out surface immediately since we have no
// need for it, and this ensures it cannot remain visible as we collapse.
@@ -2068,7 +2082,7 @@ public class BubbleStackView extends FrameLayout
hideCurrentInputMethod();
- mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
+ mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
@@ -2081,7 +2095,7 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
if (!notifPanelExpanded && mIsExpanded) {
startMonitoringSwipeUpGesture();
}
@@ -2095,7 +2109,6 @@ public class BubbleStackView extends FrameLayout
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2143,7 +2156,6 @@ public class BubbleStackView extends FrameLayout
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -2171,9 +2183,6 @@ public class BubbleStackView extends FrameLayout
}
void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
- }
if (suppressed) {
int index = getBubbleIndex(bubble);
mBubbleContainer.removeViewAt(index);
@@ -2196,13 +2205,18 @@ public class BubbleStackView extends FrameLayout
}
}
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mSensitiveNotificationProtectionActive = sensitiveNotificationProtectionActive;
+ }
+
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
*/
void hideCurrentInputMethod() {
mPositioner.setImeVisible(false, 0);
- mBubbleController.hideCurrentInputMethod();
+ mManager.hideCurrentInputMethod();
}
/** Set the stack position to whatever the positioner says. */
@@ -2308,6 +2322,8 @@ public class BubbleStackView extends FrameLayout
}
private void animateExpansion() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s",
+ mExpandedBubble != null ? mExpandedBubble.getKey() : "null");
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
@@ -2323,7 +2339,8 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2384,7 +2401,7 @@ public class BubbleStackView extends FrameLayout
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble.getExpandedView() != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setContentAlpha(0f);
mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
@@ -2433,7 +2450,7 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2476,11 +2493,6 @@ public class BubbleStackView extends FrameLayout
mManageEduView.hide();
}
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2580,6 +2592,18 @@ public class BubbleStackView extends FrameLayout
mExpandedViewTemporarilyHidden = false;
mIsBubbleSwitchAnimating = false;
mExpandedViewContainer.setAnimationMatrix(null);
+
+ // When a bubble is being dragged, the expanded view is temporarily hidden.
+ // If the motion ends with dismissing the bubble, with multiple bubbles in
+ // the stack, we'll end up here to switch to the new bubble. However, the
+ // expanded view animation might not actually set the z ordering for the
+ // expanded view correctly, because the view may still be temporarily
+ // hidden. So set it again here.
+ BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ if (bev != null) {
+ mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+ mExpandedBubble.getExpandedView().setAnimating(false);
+ }
})
.start();
}, 25);
@@ -2659,7 +2683,7 @@ public class BubbleStackView extends FrameLayout
if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
mExpandedViewContainer.animate().translationY(newExpandedViewTop);
}
- List<Animator> animList = new ArrayList();
+ 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;
@@ -2703,6 +2727,12 @@ public class BubbleStackView extends FrameLayout
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
+ // If there is a deferred reorder action, and the gesture is over, run it now.
+ if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = false;
+ updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+ }
+
return dispatched;
}
@@ -2747,19 +2777,6 @@ public class BubbleStackView extends FrameLayout
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
}
- /**
- * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
- * stack, if we're collapsed.
- */
- private void dismissMagnetizedObject() {
- if (mIsExpanded) {
- final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
- dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
- } else {
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- }
- }
-
private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
if (mIsExpanded && mBubbleData.getBubbles().size() > 1
@@ -2842,6 +2859,7 @@ public class BubbleStackView extends FrameLayout
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
+ || mSensitiveNotificationProtectionActive
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
@@ -2862,7 +2880,7 @@ public class BubbleStackView extends FrameLayout
if (!shouldShowFlyout(bubble)) {
return;
}
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
mFlyoutDragDeltaX = 0f;
clearFlyoutOnHide();
mAfterFlyoutHidden = () -> {
@@ -3004,6 +3022,9 @@ public class BubbleStackView extends FrameLayout
@VisibleForTesting
public void showManageMenu(boolean show) {
if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
+ ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s",
+ show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null"));
+
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
@@ -3011,7 +3032,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
mManageMenuScrim.setVisibility(INVISIBLE);
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
if (show) {
@@ -3025,7 +3046,7 @@ public class BubbleStackView extends FrameLayout
}
};
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
.alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
@@ -3135,10 +3156,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedBubble() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedBubble()");
- }
-
mExpandedViewContainer.removeAllViews();
if (mIsExpanded && mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
@@ -3286,9 +3303,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedView() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
- }
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
int[] paddings = mPositioner.getExpandedViewContainerPadding(
@@ -3513,7 +3527,14 @@ public class BubbleStackView extends FrameLayout
*/
void onVerticalOffsetChanged(int offset) {
// adjust dismiss view vertical position, so that it is still visible to the user
- mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
+ ViewGroup.LayoutParams lp = mDismissView.getLayoutParams();
+ if (lp instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) lp;
+ layoutParams.bottomMargin = offset;
+ mDismissView.setLayoutParams(layoutParams);
+ }
+ mMagneticTarget.setScreenVerticalOffset(offset);
+ mMagneticTarget.updateLocationOnScreen();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 000000000000..fb597a05663b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+ /** Notifies that all bubbles animated out. */
+ fun onAllBubblesAnimatedOut()
+
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+ /**
+ * Checks the current expansion state of the notification panel, and invokes [callback] with the
+ * result.
+ */
+ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+ /** Requests to hide the current input method. */
+ fun hideCurrentInputMethod()
+
+ companion object {
+
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+ override fun onAllBubblesAnimatedOut() {
+ controller.onAllBubblesAnimatedOut()
+ }
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+ controller.updateWindowFlagsForBackpress(interceptBack)
+ }
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+ controller.isNotificationPanelExpanded(callback)
+ }
+
+ override fun hideCurrentInputMethod() {
+ controller.hideCurrentInputMethod()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
new file mode 100644
index 000000000000..65f8e48eb822
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.ComponentName
+import android.os.RemoteException
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import java.util.concurrent.Executor
+
+/**
+ * A wrapper class around [TaskView] for bubble expanded views.
+ *
+ * [delegateListener] allows callers to change listeners after a task has been created.
+ */
+class BubbleTaskView(val taskView: TaskView, executor: Executor) {
+
+ /** Whether the task is already created. */
+ var isCreated = false
+ private set
+
+ /** The task id. */
+ var taskId = INVALID_TASK_ID
+ private set
+
+ /** The component name of the application running in the task. */
+ var componentName: ComponentName? = null
+ private set
+
+ /** [TaskView.Listener] for users of this class. */
+ var delegateListener: TaskView.Listener? = null
+
+ /** A [TaskView.Listener] that delegates to [delegateListener]. */
+ @get:VisibleForTesting
+ val listener = object : TaskView.Listener {
+ override fun onInitialized() {
+ delegateListener?.onInitialized()
+ }
+
+ override fun onReleased() {
+ delegateListener?.onReleased()
+ }
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ delegateListener?.onTaskCreated(taskId, name)
+ this@BubbleTaskView.taskId = taskId
+ isCreated = true
+ componentName = name
+ }
+
+ override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+ delegateListener?.onTaskVisibilityChanged(taskId, visible)
+ }
+
+ override fun onTaskRemovalStarted(taskId: Int) {
+ delegateListener?.onTaskRemovalStarted(taskId)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskId: Int) {
+ delegateListener?.onBackPressedOnTaskRoot(taskId)
+ }
+ }
+
+ init {
+ taskView.setListener(executor, listener)
+ }
+
+ /**
+ * Removes the [TaskView] from window manager.
+ *
+ * This should be called after all other cleanup animations have finished.
+ */
+ fun cleanup() {
+ if (taskId != INVALID_TASK_ID) {
+ // Ensure the task is removed from WM
+ if (ENABLE_SHELL_TRANSITIONS) {
+ taskView.removeTask()
+ } else {
+ try {
+ ActivityTaskManager.getService().removeTask(taskId)
+ } catch (e: RemoteException) {
+ Log.w(TAG, e.message ?: "")
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "BubbleTaskView"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
new file mode 100644
index 000000000000..230626f49c51
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Factory for creating [BubbleTaskView]s. */
+fun interface BubbleTaskViewFactory {
+ /** Creates a new instance of [BubbleTaskView]. */
+ fun create(): BubbleTaskView
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index da4a9898a44c..21b70b8e32da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -20,7 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -29,16 +29,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
/**
* Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
@@ -64,8 +62,7 @@ public class BubbleTaskViewHelper {
}
private final Context mContext;
- private final BubbleController mController;
- private final @ShellMainThread ShellExecutor mMainExecutor;
+ private final BubbleExpandedViewManager mExpandedViewManager;
private final BubbleTaskViewHelper.Listener mListener;
private final View mParentView;
@@ -73,7 +70,6 @@ public class BubbleTaskViewHelper {
private Bubble mBubble;
@Nullable
private PendingIntent mPendingIntent;
- private TaskViewTaskController mTaskViewTaskController;
@Nullable
private TaskView mTaskView;
private int mTaskId = INVALID_TASK_ID;
@@ -84,11 +80,8 @@ public class BubbleTaskViewHelper {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
if (mDestroyed || mInitialized) {
return;
@@ -104,10 +97,8 @@ public class BubbleTaskViewHelper {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
mParentView.post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
@@ -151,7 +142,8 @@ public class BubbleTaskViewHelper {
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ mExpandedViewManager.removeBubble(
+ getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
}
mInitialized = true;
});
@@ -164,10 +156,8 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -183,37 +173,41 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
- mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
+ mTaskView = null;
}
}
@Override
public void onBackPressedOnTaskRoot(int taskId) {
- if (mTaskId == taskId && mController.isStackExpanded()) {
+ if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
mListener.onBackPressed();
}
}
};
public BubbleTaskViewHelper(Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
BubbleTaskViewHelper.Listener listener,
+ BubbleTaskView bubbleTaskView,
View parent) {
mContext = context;
- mController = controller;
- mMainExecutor = mController.getMainExecutor();
+ mExpandedViewManager = expandedViewManager;
mListener = listener;
mParentView = parent;
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
+ if (bubbleTaskView.isCreated()) {
+ mTaskId = bubbleTaskView.getTaskId();
+ mListener.onTaskCreated();
+ }
}
/**
@@ -231,24 +225,6 @@ public class BubbleTaskViewHelper {
return false;
}
- /** Cleans up anything related to the task and {@code TaskView}. */
- public void cleanUpTaskView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (mTaskId != INVALID_TASK_ID) {
- try {
- ActivityTaskManager.getService().removeTask(mTaskId);
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- if (mTaskView != null) {
- mTaskView.release();
- mTaskView = null;
- }
- }
-
/** Returns the bubble key associated with this view. */
@Nullable
public String getBubbleKey() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index bb30c5eeebcf..69119cf4338e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -70,7 +70,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
private Bubble mBubble;
private WeakReference<Context> mContext;
- private WeakReference<BubbleController> mController;
+ private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+ private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+ private WeakReference<BubblePositioner> mPositioner;
private WeakReference<BubbleStackView> mStackView;
private WeakReference<BubbleBarLayerView> mLayerView;
private BubbleIconFactory mIconFactory;
@@ -84,7 +86,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
*/
BubbleViewInfoTask(Bubble b,
Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
@Nullable BubbleStackView stackView,
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory factory,
@@ -93,7 +97,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Executor mainExecutor) {
mBubble = b;
mContext = new WeakReference<>(context);
- mController = new WeakReference<>(controller);
+ mExpandedViewManager = new WeakReference<>(expandedViewManager);
+ mTaskViewFactory = new WeakReference<>(taskViewFactory);
+ mPositioner = new WeakReference<>(positioner);
mStackView = new WeakReference<>(stackView);
mLayerView = new WeakReference<>(layerView);
mIconFactory = factory;
@@ -109,11 +115,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
return null;
}
if (mLayerView.get() != null) {
- return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
- mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
+ return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
+ mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
+ mBubble, mSkipInflation);
} else {
- return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
- mIconFactory, mBubble, mSkipInflation);
+ return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
+ mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
+ mBubble, mSkipInflation);
}
}
@@ -135,7 +143,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
private boolean verifyState() {
- if (mController.get().isShowingAsBubbleBar()) {
+ if (mExpandedViewManager.get().isShowingAsBubbleBar()) {
return mLayerView.get() != null;
} else {
return mStackView.get() != null;
@@ -167,16 +175,23 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Bitmap badgeBitmap;
@Nullable
- public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
- BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b,
+ public static BubbleViewInfo populateForBubbleBar(Context c,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
+ BubbleBarLayerView layerView,
+ BubbleIconFactory iconFactory,
+ Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
+ info.bubbleBarExpandedView.initialize(
+ expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -189,8 +204,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@VisibleForTesting
@Nullable
- public static BubbleViewInfo populate(Context c, BubbleController controller,
- BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
+ public static BubbleViewInfo populate(Context c,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
+ BubbleStackView stackView,
+ BubbleIconFactory iconFactory,
+ Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
@@ -199,11 +219,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
- info.imageView.initialize(controller.getPositioner());
+ info.imageView.initialize(positioner);
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(controller, stackView, false /* isOverflow */);
+ info.expandedView.initialize(
+ expandedViewManager, stackView, positioner, false /* isOverflow */,
+ bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
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 759246eb285d..26077cf7057b 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
@@ -286,6 +286,16 @@ public interface Bubbles {
void onUserRemoved(int removedUserId);
/**
+ * Called when the Sensitive notification protection state has changed, such as when media
+ * projection starts and stops.
+ *
+ * @param sensitiveNotificationProtectionActive {@code true} if notifications should be
+ * protected
+ */
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive);
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
@@ -321,6 +331,13 @@ public interface Bubbles {
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
+
+ /** Provider interface for {@link SysuiProxy}. */
+ interface Provider {
+ /** Returns {@link SysuiProxy}. */
+ SysuiProxy getSysuiProxy();
+ }
+
void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
index 9e8a385262e4..c1f704ab455d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -24,8 +24,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
/**
* Observer used to identify tasks that are opening or moving to front. If a bubble activity is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5776ad109d19..7a5afec934f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
+import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
/**
@@ -29,7 +30,7 @@ interface IBubbles {
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
+ oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
oneway void removeBubble(in String key) = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 61e17c8ec459..da71b1c741bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -33,15 +33,16 @@ import com.android.wm.shell.animation.Interpolators
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
-
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
+class ManageEducationView(
+ context: Context,
+ private val positioner: BubblePositioner
+) : LinearLayout(context) {
+
+ companion object {
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ }
- private val positioner: BubblePositioner = positioner
private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
@@ -128,7 +129,7 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateManageEducationSeen()
}
/**
@@ -218,13 +219,11 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
}
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateManageEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .putBoolean(PREF_MANAGED_EDUCATION, true)
.apply()
}
}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
index 8271014d290e..08c70314973e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
@@ -1,2 +1,6 @@
# WM shell sub-module bubble owner
madym@google.com
+atsjenk@google.com
+liranb@google.com
+sukeshram@google.com
+mpodolian@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 2cabb65abe7a..c4108c4129e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -34,19 +34,21 @@ import com.android.wm.shell.animation.Interpolators
*/
class StackEducationView(
context: Context,
- positioner: BubblePositioner,
- controller: BubbleController
+ private val positioner: BubblePositioner,
+ private val manager: Manager
) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
+ companion object {
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ private const val ANIMATE_DURATION_SHORT: Long = 40
+ }
- private val positioner: BubblePositioner = positioner
- private val controller: BubbleController = controller
+ /** Callbacks to notify managers of [StackEducationView] about events. */
+ interface Manager {
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+ }
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
@@ -97,7 +99,7 @@ class StackEducationView(
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setOnKeyListener(null)
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
}
private fun setTextColor() {
@@ -128,7 +130,7 @@ class StackEducationView(
isHiding = false
if (visibility == VISIBLE) return false
- controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(true /* interceptBack */)
layoutParams.width =
if (positioner.isLargeScreen || positioner.isLandscape)
context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -175,7 +177,7 @@ class StackEducationView(
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateStackEducationSeen()
return true
}
@@ -189,20 +191,18 @@ class StackEducationView(
if (visibility != VISIBLE || isHiding) return
isHiding = true
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
.setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
.withEndAction { visibility = GONE }
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateStackEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .putBoolean(PREF_STACK_EDUCATION, true)
.apply()
}
}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
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 5b0239f6d659..7798aa753aa2 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
@@ -420,7 +420,7 @@ public class ExpandedAnimationController
bubbleView.setTranslationY(y);
}
- final float expandedY = mPositioner.getExpandedViewYTopAligned();
+ final int expandedY = mPositioner.getExpandedViewYTopAligned();
final boolean draggedOutEnough =
y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
@@ -563,7 +563,7 @@ public class ExpandedAnimationController
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@ public class ExpandedAnimationController
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
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
index 845dca34b41f..e43609fe8ff0 100644
--- 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
@@ -15,14 +15,11 @@
*/
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 static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,7 +28,6 @@ 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;
@@ -41,6 +37,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.bubbles.BubbleExpandedView;
@@ -55,8 +52,6 @@ import java.util.List;
*/
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;
@@ -121,9 +116,6 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@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();
}
@@ -140,17 +132,14 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
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);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount);
setCollapsedAmount(mDraggedAmount);
if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
mNotifiedAboutThreshold = true;
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "notifying over collapse threshold");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold");
vibrateIfEnabled();
}
}
@@ -172,45 +161,35 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
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);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "not collapsing expanded view, swipe down velocity=%f minV=%d",
+ mSwipeDownVelocity, 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);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, swipe up velocity=%f minV=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
return true;
}
if (isPastCollapseThreshold()) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, past threshold, dragged=%d", mDraggedAmount);
return true;
}
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "not collapsing expanded view");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "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);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -243,9 +222,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void animateBackToExpanded() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "expandedView animate back to expanded");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded");
BubbleExpandedView expandedView = mExpandedView;
if (expandedView == null) {
return;
@@ -298,9 +275,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void reset() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "reset expandedView collapsed state");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e48732801094..bb0dd95b042f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -769,8 +769,10 @@ public class StackAnimationController extends
boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
- final int oldIndex = mLayout.indexOfChild(view);
- swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ if (view != null) {
+ final int oldIndex = mLayout.indexOfChild(view);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
}
if (!swapped) {
// All bubbles were at the right position. Make sure badges and z order is correct.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 7f34ee0cdd3d..8946f41e96a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -15,14 +15,29 @@
*/
package com.android.wm.shell.bubbles.bar;
+import static android.view.View.ALPHA;
+import static android.view.View.SCALE_X;
+import static android.view.View.SCALE_Y;
+import static android.view.View.TRANSLATION_X;
+import static android.view.View.TRANSLATION_Y;
import static android.view.View.VISIBLE;
+import static android.view.View.X;
+import static android.view.View.Y;
+
+import static com.android.wm.shell.animation.Interpolators.EMPHASIZED;
+import static com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Log;
+import android.util.Size;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -33,6 +48,7 @@ import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget;
/**
* Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
@@ -44,6 +60,17 @@ public class BubbleBarAnimationHelper {
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400;
+ private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400;
+ private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 400;
+ /**
+ * Additional scale applied to expanded view when it is positioned inside a magnetic target.
+ */
+ private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.2f;
+ private static final float EXPANDED_VIEW_DRAG_SCALE = 0.4f;
+ private static final float DISMISS_VIEW_SCALE = 1.25f;
+ private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100;
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -59,9 +86,13 @@ public class BubbleBarAnimationHelper {
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+ @Nullable
+ private Animator mRunningDragAnimator;
+
private final Context mContext;
private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
+ private final int[] mTmpLocation = new int[2];
private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded = false;
@@ -136,12 +167,12 @@ public class BubbleBarAnimationHelper {
bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarPosition.x,
- bubbleBarPosition.y);
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -181,7 +212,8 @@ public class BubbleBarAnimationHelper {
Log.w(TAG, "Trying to animate collapse without a bubble");
return;
}
-
+ bbev.setScaleX(1f);
+ bbev.setScaleY(1f);
mExpandedViewContainerMatrix.setScaleX(1f);
mExpandedViewContainerMatrix.setScaleY(1f);
@@ -209,11 +241,210 @@ public class BubbleBarAnimationHelper {
}
/**
+ * Animate the expanded bubble when it is being dragged
+ */
+ public void animateStartDrag() {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate start drag without a bubble");
+ return;
+ }
+ setDragPivot(bbev);
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
+
+ AnimatorSet contentAnim = new AnimatorSet();
+ contentAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius)
+ );
+ contentAnim.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED);
+
+ ObjectAnimator handleAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f)
+ .setDuration(HANDLE_ALPHA_ANIMATION_DURATION);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(contentAnim, handleAnim);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animates dismissal of currently expanded bubble
+ *
+ * @param endRunnable a runnable to run at the end of the animation
+ */
+ public void animateDismiss(Runnable endRunnable) {
+ mIsExpanded = false;
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate dismiss without a bubble");
+ return;
+ }
+
+ int[] location = bbev.getLocationOnScreen();
+ int diffFromBottom = mPositioner.getScreenRect().bottom - location[1];
+
+ cancelAnimations();
+ bbev.animate()
+ // 2x distance from bottom so the view flies out
+ .translationYBy(diffFromBottom * 2)
+ .setDuration(EXPANDED_VIEW_DISMISS_DURATION)
+ .withEndAction(endRunnable)
+ .start();
+ }
+
+ /**
+ * Animate current expanded bubble back to its rest position
+ */
+ public void animateToRestPosition() {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate expanded view to rest position without a bubble");
+ return;
+ }
+ Point restPoint = getExpandedViewRestPosition(getExpandedViewSize());
+
+ AnimatorSet contentAnim = new AnimatorSet();
+ contentAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, X, restPoint.x),
+ ObjectAnimator.ofFloat(bbev, Y, restPoint.y),
+ ObjectAnimator.ofFloat(bbev, SCALE_X, 1f),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, 1f),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, bbev.getRestingCornerRadius())
+ );
+ contentAnim.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED);
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f)
+ .setDuration(HANDLE_ALPHA_ANIMATION_DURATION);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(contentAnim, handleAlphaAnim);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ bbev.resetPivot();
+ }
+ });
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animates currently expanded bubble into the given {@link MagneticTarget}.
+ *
+ * @param target magnetic target to snap to
+ * @param endRunnable a runnable to run at the end of the animation
+ */
+ public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
+ return;
+ }
+
+ setDragPivot(bbev);
+
+ // When the view animates into the target, it is scaled down with the pivot at center top.
+ // Find the point on the view that would be the center of the view at its final scale.
+ // Once we know that, we can calculate x and y distance from the center of the target view
+ // and use that for the translation animation to ensure that the view at final scale is
+ // placed at the center of the target.
+
+ // Set mTmpLocation to the current location of the view on the screen, taking into account
+ // any scale applied.
+ bbev.getLocationOnScreen(mTmpLocation);
+ // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+ // x-axis will be the same as the center of the view at current size.
+ // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+ // center of the view at its current size.
+ float currentWidth = bbev.getWidth() * bbev.getScaleX();
+ mTmpLocation[0] += (int) (currentWidth / 2f);
+ // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+ // remains the same.
+ // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+ // moved down by half of the height at final scale.
+ float targetHeight = bbev.getHeight() * EXPANDED_VIEW_IN_TARGET_SCALE;
+ mTmpLocation[1] += (int) (targetHeight / 2f);
+ // mTmpLocation is now set to the point on the view that will be the center of the view once
+ // scale is applied.
+
+ // Calculate the difference between the target's center coordinates and mTmpLocation
+ float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+ float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
+
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_IN_TARGET_SCALE;
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ // Move expanded view to the center of dismiss view
+ ObjectAnimator.ofFloat(bbev, TRANSLATION_X, bbev.getTranslationX() + xDiff),
+ ObjectAnimator.ofFloat(bbev, TRANSLATION_Y, bbev.getTranslationY() + yDiff),
+ // Scale expanded view down
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_IN_TARGET_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_IN_TARGET_SCALE),
+ // Update corner radius for expanded view
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius),
+ // Scale dismiss view up
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, DISMISS_VIEW_SCALE),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, DISMISS_VIEW_SCALE)
+ );
+ animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
+ EMPHASIZED_DECELERATE);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ }
+ });
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animate currently expanded view when it is released from dismiss view
+ */
+ public void animateUnstuckFromDismissView(MagneticTarget target) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble");
+ return;
+ }
+ setDragPivot(bbev);
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, 1f),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, 1f)
+ );
+ animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
+ EMPHASIZED_DECELERATE);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
* Cancel current animations
*/
public void cancelAnimations() {
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
mExpandedViewAlphaAnimator.cancel();
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev != null) {
+ bbev.animate().cancel();
+ }
+ if (mRunningDragAnimator != null) {
+ mRunningDragAnimator.cancel();
+ mRunningDragAnimator = null;
+ }
}
private @Nullable BubbleBarExpandedView getExpandedView() {
@@ -231,21 +462,65 @@ public class BubbleBarAnimationHelper {
return;
}
- boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
- final int padding = mPositioner.getBubbleBarExpandedViewPadding();
- final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
- final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+ final Size size = getExpandedViewSize();
+ Point position = getExpandedViewRestPosition(size);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
- lp.width = width;
- lp.height = height;
+ lp.width = size.getWidth();
+ lp.height = size.getHeight();
bbev.setLayoutParams(lp);
+ bbev.setX(position.x);
+ bbev.setY(position.y);
+ bbev.updateLocation();
+ bbev.maybeShowOverflow();
+ }
+
+ private Point getExpandedViewRestPosition(Size size) {
+ final int padding = mPositioner.getBubbleBarExpandedViewPadding();
+ Point point = new Point();
if (mLayerView.isOnLeft()) {
- bbev.setX(mPositioner.getInsets().left + padding);
+ point.x = mPositioner.getInsets().left + padding;
} else {
- bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
+ point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
+ }
+ point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight();
+ return point;
+ }
+
+ private Size getExpandedViewSize() {
+ boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+ return new Size(width, height);
+ }
+
+ private void startNewDragAnimation(Animator animator) {
+ cancelAnimations();
+ mRunningDragAnimator = animator;
+ animator.start();
+ }
+
+ private static void setDragPivot(BubbleBarExpandedView bbev) {
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0f);
+ }
+
+ private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
+
+ private final BubbleBarExpandedView mBubbleBarExpandedView;
+
+ DragAnimatorListenerAdapter(BubbleBarExpandedView bbev) {
+ mBubbleBarExpandedView = bbev;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBubbleBarExpandedView.setAnimating(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBubbleBarExpandedView.setAnimating(false);
+ mRunningDragAnimator = null;
}
- bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
- bbev.updateLocation();
- bbev.maybeShowOverflow();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index d073f1df938a..271fb9abce6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -25,27 +27,26 @@ import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
-import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
import java.util.function.Supplier;
-/**
- * Expanded view of a bubble when it's part of the bubble bar.
- *
- * {@link BubbleController#isShowingAsBubbleBar()}
- */
+/** Expanded view of a bubble when it's part of the bubble bar. */
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
/**
* The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
@@ -60,24 +61,46 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
void onBackPressed();
}
+ /**
+ * A property wrapper around corner radius for the expanded view, handled by
+ * {@link #setCornerRadius(float)} and {@link #getCornerRadius()} methods.
+ */
+ public static final FloatProperty<BubbleBarExpandedView> CORNER_RADIUS = new FloatProperty<>(
+ "cornerRadius") {
+ @Override
+ public void setValue(BubbleBarExpandedView bbev, float radius) {
+ bbev.setCornerRadius(radius);
+ }
+
+ @Override
+ public Float get(BubbleBarExpandedView bbev) {
+ return bbev.getCornerRadius();
+ }
+ };
+
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
- private BubbleController mController;
+ private BubbleExpandedViewManager mManager;
private boolean mIsOverflow;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
private BubbleBarMenuViewController mMenuViewController;
private @Nullable Supplier<Rect> mLayerBoundsSupplier;
private @Nullable Listener mListener;
- private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+ private BubbleBarHandleView mHandleView;
private @Nullable TaskView mTaskView;
private @Nullable BubbleOverflowContainerView mOverflowView;
private int mCaptionHeight;
private int mBackgroundColor;
- private float mCornerRadius = 0f;
+ /** Corner radius used when view is resting */
+ private float mRestingCornerRadius = 0f;
+ /** Corner radius applied while dragging */
+ private float mDraggedCornerRadius = 0f;
+ /** Current corner radius */
+ private float mCurrentCornerRadius = 0f;
/**
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
@@ -111,15 +134,17 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
mCaptionHeight = context.getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_caption_height);
- addView(mHandleView);
+ mHandleView = findViewById(R.id.bubble_bar_handle_view);
applyThemeAttrs();
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCurrentCornerRadius);
}
});
+ // Set a touch sink to ensure that clicks on the caption area do not propagate to the parent
+ setOnTouchListener((v, event) -> true);
}
@Override
@@ -129,25 +154,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mMenuViewController.hideMenu(false /* animated */);
}
- /** Set the BubbleController on the view, must be called before doing anything else. */
- public void initialize(BubbleController controller, boolean isOverflow) {
- mController = controller;
+ /** Initializes the view, must be called before doing anything else. */
+ public void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubblePositioner positioner,
+ boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
+ mManager = expandedViewManager;
mIsOverflow = isOverflow;
if (mIsOverflow) {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_overflow_container, null /* root */);
- mOverflowView.setBubbleController(mController);
+ mOverflowView.initialize(expandedViewManager, positioner);
addView(mOverflowView);
} else {
-
- mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
- /* listener= */ this,
+ mTaskView = bubbleTaskView.getTaskView();
+ mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
+ /* listener= */ this, bubbleTaskView,
/* viewParent= */ this);
- mTaskView = mBubbleTaskViewHelper.getTaskView();
- addView(mTaskView);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ addView(mTaskView, lp);
mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCurrentCornerRadius);
+ mTaskView.setVisibility(VISIBLE);
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
@@ -168,13 +201,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onOpenAppSettings(Bubble bubble) {
- mController.collapseStack();
+ mManager.collapseStack();
mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
}
@Override
public void onDismissBubble(Bubble bubble) {
- mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+ mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
}
});
mHandleView.setOnClickListener(view -> {
@@ -189,22 +222,24 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
// TODO (b/275087636): call this when theme/config changes
/** Updates the view based on the current theme. */
public void applyThemeAttrs() {
- boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources());
+ mRestingCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_corner_radius
+ );
+ mDraggedCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_corner_radius_dragged
+ );
+
+ mCurrentCornerRadius = mRestingCornerRadius;
+
final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
- android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
- mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
- mCornerRadius = mCornerRadius / 2f;
- mBackgroundColor = ta.getColor(1, Color.WHITE);
-
+ mBackgroundColor = ta.getColor(0, Color.WHITE);
ta.recycle();
-
mCaptionHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_caption_height);
if (mTaskView != null) {
- mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCurrentCornerRadius);
updateHandleColor(true /* animated */);
}
}
@@ -212,12 +247,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- int menuViewHeight = Math.min(mCaptionHeight, height);
- measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
-
if (mTaskView != null) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
@@ -226,14 +257,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
mTaskView.layout(l, t, r,
t + mTaskView.getMeasuredHeight());
mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
- // Handle draws on top of task view in the caption area.
- mHandleView.layout(l, t, r, captionBottom);
}
@Override
@@ -256,14 +284,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- mBubbleTaskViewHelper.cleanUpTaskView();
- }
mMenuViewController.hideMenu(false /* animated */);
}
@@ -275,7 +297,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (mMenuViewController.isMenuVisible()) {
mMenuViewController.hideMenu(/* animated = */ true);
} else {
- mController.collapseStack();
+ mManager.collapseStack();
}
}
@@ -393,4 +415,30 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
public boolean isAnimating() {
return mIsAnimating;
}
+
+ /** @return corner radius that should be applied while view is in rest */
+ public float getRestingCornerRadius() {
+ return mRestingCornerRadius;
+ }
+
+ /** @return corner radius that should be applied while view is being dragged */
+ public float getDraggedCornerRadius() {
+ return mDraggedCornerRadius;
+ }
+
+ /** @return current corner radius */
+ public float getCornerRadius() {
+ return mCurrentCornerRadius;
+ }
+
+ /** Update corner radius */
+ public void setCornerRadius(float cornerRadius) {
+ if (mCurrentCornerRadius != cornerRadius) {
+ mCurrentCornerRadius = cornerRadius;
+ if (mTaskView != null) {
+ mTaskView.setCornerRadius(cornerRadius);
+ }
+ invalidateOutline();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 4ea18f78f5b2..7d37d6068dfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -16,70 +16,70 @@
package com.android.wm.shell.bubbles.bar
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.graphics.PointF
-import android.graphics.Rect
+import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
-import com.android.wm.shell.animation.Interpolators
import com.android.wm.shell.common.bubbles.DismissView
import com.android.wm.shell.common.bubbles.RelativeTouchListener
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject
/** Controller for handling drag interactions with [BubbleBarExpandedView] */
+@SuppressLint("ClickableViewAccessibility")
class BubbleBarExpandedViewDragController(
private val expandedView: BubbleBarExpandedView,
private val dismissView: DismissView,
+ private val animationHelper: BubbleBarAnimationHelper,
private val onDismissed: () -> Unit
) {
+ var isStuckToDismiss: Boolean = false
+ private set
+
+ private var expandedViewInitialTranslationX = 0f
+ private var expandedViewInitialTranslationY = 0f
+ private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
+ MagnetizedObject.magnetizeView(expandedView)
+ private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+
init {
- expandedView.handleView.setOnTouchListener(HandleDragListener())
- }
+ magnetizedExpandedView.magnetListener = MagnetListener()
+ magnetizedExpandedView.animateStuckToTarget =
+ {
+ target: MagnetizedObject.MagneticTarget,
+ _: Float,
+ _: Float,
+ _: Boolean,
+ after: (() -> Unit)? ->
+ animationHelper.animateIntoTarget(target, after)
+ }
- private fun finishDrag(x: Float, y: Float, viewInitialX: Float, viewInitialY: Float) {
- val dismissCircleBounds = Rect().apply { dismissView.circle.getBoundsOnScreen(this) }
- if (dismissCircleBounds.contains(x.toInt(), y.toInt())) {
- onDismissed()
- } else {
- resetExpandedViewPosition(viewInitialX, viewInitialY)
- }
- dismissView.hide()
- }
+ magnetizedDismissTarget =
+ MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
+ magnetizedExpandedView.addTarget(magnetizedDismissTarget)
- private fun resetExpandedViewPosition(initialX: Float, initialY: Float) {
- val listener =
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- expandedView.isAnimating = true
- }
+ val dragMotionEventHandler = HandleDragListener()
- override fun onAnimationEnd(animation: Animator) {
- expandedView.isAnimating = false
- }
+ expandedView.handleView.setOnTouchListener { view, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ expandedViewInitialTranslationX = expandedView.translationX
+ expandedViewInitialTranslationY = expandedView.translationY
}
- expandedView
- .animate()
- .translationX(initialX)
- .translationY(initialY)
- .setDuration(RESET_POSITION_ANIM_DURATION)
- .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
- .setListener(listener)
- .start()
+ val magnetConsumed = magnetizedExpandedView.maybeConsumeMotionEvent(event)
+ // Move events can be consumed by the magnetized object
+ if (event.actionMasked == MotionEvent.ACTION_MOVE && magnetConsumed) {
+ return@setOnTouchListener true
+ }
+ return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
+ }
}
private inner class HandleDragListener : RelativeTouchListener() {
- private val expandedViewRestPosition = PointF()
+ private var isMoving = false
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
- if (expandedView.isAnimating) {
- return false
- }
- expandedViewRestPosition.x = expandedView.translationX
- expandedViewRestPosition.y = expandedView.translationY
- return true
+ return !expandedView.isAnimating
}
override fun onMove(
@@ -90,8 +90,12 @@ class BubbleBarExpandedViewDragController(
dx: Float,
dy: Float
) {
- expandedView.translationX = expandedViewRestPosition.x + dx
- expandedView.translationY = expandedViewRestPosition.y + dy
+ if (!isMoving) {
+ isMoving = true
+ animationHelper.animateStartDrag()
+ }
+ expandedView.translationX = expandedViewInitialTranslationX + dx
+ expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
}
@@ -105,16 +109,48 @@ class BubbleBarExpandedViewDragController(
velX: Float,
velY: Float
) {
- finishDrag(ev.rawX, ev.rawY, expandedViewRestPosition.x, expandedViewRestPosition.y)
+ finishDrag()
}
override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
- resetExpandedViewPosition(expandedViewRestPosition.x, expandedViewRestPosition.y)
- dismissView.hide()
+ finishDrag()
+ }
+
+ private fun finishDrag() {
+ if (!isStuckToDismiss) {
+ animationHelper.animateToRestPosition()
+ dismissView.hide()
+ }
+ isMoving = false
}
}
- companion object {
- const val RESET_POSITION_ANIM_DURATION = 300L
+ private inner class MagnetListener : MagnetizedObject.MagnetListener {
+ override fun onStuckToTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
+ ) {
+ isStuckToDismiss = true
+ }
+
+ override fun onUnstuckFromTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>,
+ velX: Float,
+ velY: Float,
+ wasFlungOut: Boolean
+ ) {
+ isStuckToDismiss = false
+ animationHelper.animateUnstuckFromDismissView(target)
+ }
+
+ override fun onReleasedInTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
+ ) {
+ onDismissed()
+ dismissView.hide()
+ }
}
}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bdb0e206e490..42799d975e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.bar;
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.Bubbles.DISMISS_USER_GESTURE;
import android.annotation.Nullable;
import android.content.Context;
@@ -25,6 +26,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -32,11 +34,12 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
-import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
@@ -60,6 +63,7 @@ public class BubbleBarLayerView extends FrameLayout
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -74,10 +78,6 @@ public class BubbleBarLayerView extends FrameLayout
private DismissView mDismissView;
private @Nullable Consumer<String> mUnBubbleConversationCallback;
- // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
- /** Whether the expanded view is displaying on the left of the screen or not. */
- private boolean mOnLeft = false;
-
/** Whether a bubble is expanded. */
private boolean mIsExpanded = false;
@@ -88,9 +88,10 @@ public class BubbleBarLayerView extends FrameLayout
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -154,10 +155,10 @@ public class BubbleBarLayerView extends FrameLayout
return mIsExpanded;
}
- // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+ // TODO(b/313661121) - when dragging is implemented, check user setting first
/** Whether the expanded view is positioned on the left or right side of the screen. */
public boolean isOnLeft() {
- return mOnLeft;
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
/** Shows the expanded view of the provided bubble. */
@@ -206,14 +207,17 @@ public class BubbleBarLayerView extends FrameLayout
}
});
- mDragController = new BubbleBarExpandedViewDragController(mExpandedView, mDismissView,
+ mDragController = new BubbleBarExpandedViewDragController(
+ mExpandedView,
+ mDismissView,
+ mAnimationHelper,
() -> {
mBubbleController.dismissBubble(mExpandedBubble.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
+ DISMISS_USER_GESTURE);
return Unit.INSTANCE;
});
- addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+ addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
}
if (mEducationViewController.isEducationVisible()) {
@@ -227,6 +231,7 @@ public class BubbleBarLayerView extends FrameLayout
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
view.getBoundsOnScreen(mHandleTouchBounds);
+ // Move top value up to ensure touch target is large enough
mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
@@ -236,12 +241,55 @@ public class BubbleBarLayerView extends FrameLayout
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble, Runnable endAction) {
+ Runnable cleanUp = () -> {
+ bubble.cleanupViews();
+ endAction.run();
+ };
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(cleanUp);
+ } else {
+ cleanUp.run();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ if (endAction != null) {
+ endAction.run();
+ }
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ if (mBubbleData.getBubbles().isEmpty()) {
+ mBubbleController.onAllBubblesAnimatedOut();
+ }
+ };
+ if (mDragController != null && mDragController.isStuckToDismiss()) {
+ mAnimationHelper.animateDismiss(runnable);
+ } else {
+ mAnimationHelper.animateCollapse(runnable);
+ }
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
mDragController = null;
@@ -304,7 +352,7 @@ public class BubbleBarLayerView extends FrameLayout
lp.width = width;
lp.height = height;
mExpandedView.setLayoutParams(lp);
- if (mOnLeft) {
+ if (isOnLeft()) {
mExpandedView.setX(mPositioner.getInsets().left + padding);
} else {
mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index e1dea3babbc2..33b61b164988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -17,18 +17,19 @@
package com.android.wm.shell.bubbles.properties
import android.os.SystemProperties
+import com.android.wm.shell.Flags
/** Provides bubble properties in production. */
object ProdBubbleProperties : BubbleProperties {
- // TODO(b/256873975) Should use proper flag when available to shell/launcher
- private var _isBubbleBarEnabled =
+ private var _isBubbleBarEnabled = Flags.enableBubbleBar() ||
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
override val isBubbleBarEnabled
get() = _isBubbleBarEnabled
override fun refresh() {
- _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ _isBubbleBarEnabled = Flags.enableBubbleBar() ||
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 2ea43162d225..ad01d0fa311a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -20,12 +20,12 @@ import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
-import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
@@ -51,6 +51,7 @@ import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -122,7 +123,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (mDisplayController.getDisplayLayout(displayId).rotation()
!= pd.mRotation && isImeShowing(displayId)) {
- pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
+ pd.startAnimation(true, false /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED);
}
}
@@ -257,7 +259,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mInsetsState.set(insetsState, true /* copySources */);
if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
- startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+ startAnimation(mImeShowing, true /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_INSETS_CHANGED);
}
}
@@ -291,7 +294,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final boolean positionChanged =
!imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
- startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+ startAnimation(mImeShowing, true /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED);
}
} else {
if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
@@ -384,7 +388,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
private void startAnimation(final boolean show, final boolean forceRestart,
- @Nullable ImeTracker.Token statsToken) {
+ @SoftInputShowHideReason int reason) {
+ final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ if (imeSource == null || mImeSourceControl == null) {
+ return;
+ }
+ final var statsToken = ImeTracker.forLogging().onStart(
+ show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL,
+ reason, false /* fromUser */);
+
+ startAnimation(show, forceRestart, statsToken);
+ }
+
+ private void startAnimation(final boolean show, final boolean forceRestart,
+ @NonNull final ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
if (imeSource == null || mImeSourceControl == null) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
@@ -458,7 +475,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
- @Nullable
+ @NonNull
private final ImeTracker.Token mStatsToken = statsToken;
@Override
@@ -484,7 +501,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
mDisplayId, mAnimationDirection, alpha, startY , endY,
Objects.toString(mImeSourceControl.getLeash()),
Objects.toString(mImeSourceControl.getInsetsHint()),
@@ -500,7 +517,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mCancelled = true;
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+ mDisplayId,
Objects.toString(mImeSourceControl.getInsetsHint()));
}
}
@@ -528,7 +546,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
mDisplayId, mAnimationDirection, endY,
Objects.toString(mImeSourceControl.getLeash()),
Objects.toString(mImeSourceControl.getInsetsHint()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 9bdda14cf00b..ca06024a9adb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -277,8 +277,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
*
* @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
- * @param statsToken the token tracking the current IME show request
- * or {@code null} otherwise.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
default void showInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {}
@@ -288,8 +287,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
*
* @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
- * @param statsToken the token tracking the current IME hide request
- * or {@code null} otherwise.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
default void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 1959eb03a6b3..86cec02ab138 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -22,12 +22,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
-import static android.util.RotationUtils.rotateBounds;
-import static android.util.RotationUtils.rotateInsets;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -40,11 +35,9 @@ import android.graphics.Rect;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.DisplayMetrics;
-import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
-import android.view.Gravity;
import android.view.InsetsState;
import android.view.Surface;
import android.view.WindowInsets;
@@ -226,25 +219,22 @@ public class DisplayLayout {
/**
* Apply a rotation to this layout and its parameters.
- * @param res
- * @param targetRotation
*/
- public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
- final int rotationDelta = (targetRotation - mRotation + 4) % 4;
- final boolean changeOrient = (rotationDelta % 2) != 0;
-
+ public void rotateTo(Resources res, @Surface.Rotation int toRotation) {
final int origWidth = mWidth;
final int origHeight = mHeight;
+ final int fromRotation = mRotation;
+ final int rotationDelta = (toRotation - fromRotation + 4) % 4;
+ final boolean changeOrient = (rotationDelta % 2) != 0;
- mRotation = targetRotation;
+ mRotation = toRotation;
if (changeOrient) {
mWidth = origHeight;
mHeight = origWidth;
}
- if (mCutout != null && !mCutout.isEmpty()) {
- mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
- origHeight);
+ if (mCutout != null) {
+ mCutout = mCutout.getRotated(origWidth, origHeight, fromRotation, toRotation);
}
recalcInsets(res);
@@ -398,96 +388,6 @@ public class DisplayLayout {
}
}
- /** Calculate the DisplayCutout for a particular display size/rotation. */
- public static DisplayCutout calculateDisplayCutoutForRotation(
- DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
- if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
- return null;
- }
- if (rotation == ROTATION_0) {
- return computeSafeInsets(cutout, displayWidth, displayHeight);
- }
- final Insets waterfallInsets = rotateInsets(cutout.getWaterfallInsets(), rotation);
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- Rect[] cutoutRects = cutout.getBoundingRectsAll();
- final Rect[] newBounds = new Rect[cutoutRects.length];
- final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
- for (int i = 0; i < cutoutRects.length; ++i) {
- final Rect rect = new Rect(cutoutRects[i]);
- if (!rect.isEmpty()) {
- rotateBounds(rect, displayBounds, rotation);
- }
- newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
- }
- final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
- final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
- info.getDisplayWidth(), info.getDisplayHeight(), info.getPhysicalDisplayWidth(),
- info.getPhysicalDisplayHeight(), info.getDensity(), info.getCutoutSpec(), rotation,
- info.getScale(), info.getPhysicalPixelDisplaySizeRatio());
- return computeSafeInsets(
- DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? displayHeight : displayWidth,
- rotated ? displayWidth : displayHeight);
- }
-
- private static int getBoundIndexFromRotation(int index, int rotation) {
- return (index - rotation) < 0
- ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
- : index - rotation;
- }
-
- /** Calculate safe insets. */
- public static DisplayCutout computeSafeInsets(DisplayCutout inner,
- int displayWidth, int displayHeight) {
- if (inner == DisplayCutout.NO_CUTOUT) {
- return null;
- }
-
- final Size displaySize = new Size(displayWidth, displayHeight);
- final Rect safeInsets = computeSafeInsets(displaySize, inner);
- return inner.replaceSafeInsets(safeInsets);
- }
-
- private static Rect computeSafeInsets(
- Size displaySize, DisplayCutout cutout) {
- if (displaySize.getWidth() == displaySize.getHeight()) {
- throw new UnsupportedOperationException("not implemented: display=" + displaySize
- + " cutout=" + cutout);
- }
-
- int leftInset = Math.max(cutout.getWaterfallInsets().left,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
- int topInset = Math.max(cutout.getWaterfallInsets().top,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
- int rightInset = Math.max(cutout.getWaterfallInsets().right,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
- int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
- Gravity.BOTTOM));
-
- return new Rect(leftInset, topInset, rightInset, bottomInset);
- }
-
- private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
- if (boundingRect.isEmpty()) {
- return 0;
- }
-
- int inset = 0;
- switch (gravity) {
- case Gravity.TOP:
- return Math.max(inset, boundingRect.bottom);
- case Gravity.BOTTOM:
- return Math.max(inset, display.getHeight() - boundingRect.top);
- case Gravity.LEFT:
- return Math.max(inset, boundingRect.right);
- case Gravity.RIGHT:
- return Math.max(inset, display.getWidth() - boundingRect.left);
- default:
- throw new IllegalArgumentException("unknown gravity: " + gravity);
- }
- }
-
static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
// Allow a system property to override this. Used by the emulator.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
new file mode 100644
index 000000000000..4c34971c4fb1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.view.WindowManager
+import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
+import com.android.wm.shell.util.KtProtoLog
+import java.util.Arrays
+
+/**
+ * Helper for multi-instance related checks.
+ */
+class MultiInstanceHelper @JvmOverloads constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ private val staticAppsSupportingMultiInstance: Array<String> = context.resources
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit)) {
+
+ /**
+ * Returns whether a specific component desires to be launched in multiple instances.
+ */
+ @VisibleForTesting
+ fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean {
+ if (componentName == null || componentName.packageName == null) {
+ // TODO(b/262864589): Handle empty component case
+ return false
+ }
+
+ // Check the pre-defined allow list
+ val packageName = componentName.packageName
+ for (pkg in staticAppsSupportingMultiInstance) {
+ if (pkg == packageName) {
+ KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
+ packageName)
+ return true
+ }
+ }
+
+ // Check the activity property first
+ try {
+ val activityProp = packageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName)
+ // If the above call doesn't throw a NameNotFoundException, then the activity property
+ // should override the application property value
+ if (activityProp.isBoolean) {
+ KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
+ return activityProp.boolean
+ } else {
+ KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
+ }
+ } catch (nnfe: PackageManager.NameNotFoundException) {
+ // Not specified in the activity, fall through
+ }
+
+ // Check the application property otherwise
+ try {
+ val appProp = packageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
+ if (appProp.isBoolean) {
+ KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
+ return appProp.boolean
+ } else {
+ KtProtoLog.w(WM_SHELL,
+ "Warning: property=%s for application=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
+ }
+ } catch (nnfe: PackageManager.NameNotFoundException) {
+ // Not specified in either application or activity
+ }
+ return false
+ }
+
+ companion object {
+ /** Returns the component from a PendingIntent */
+ @JvmStatic
+ fun getComponent(pendingIntent: PendingIntent?): ComponentName? {
+ return pendingIntent?.intent?.component
+ }
+
+ /** Returns the component from a shortcut */
+ @JvmStatic
+ fun getShortcutComponent(packageName: String, shortcutId: String,
+ user: UserHandle, launcherApps: LauncherApps): ComponentName? {
+ val query = LauncherApps.ShortcutQuery()
+ query.setPackage(packageName)
+ query.setShortcutIds(Arrays.asList(shortcutId))
+ query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED)
+ val shortcuts = launcherApps.getShortcuts(query, user)
+ val info = if (shortcuts != null && shortcuts.size > 0) shortcuts[0] else null
+ return info?.activity
+ }
+
+ /** Returns true if package names and user ids match. */
+ @JvmStatic
+ fun samePackage(packageName1: String?, packageName2: String?,
+ userId1: Int, userId2: Int): Boolean {
+ return (packageName1 != null && packageName1 == packageName2) && (userId1 == userId2)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 1c74f415ec90..e4cf6d13cb1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -49,6 +49,7 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
import com.android.internal.os.IResultReceiver;
@@ -196,7 +197,7 @@ public class SystemWindows {
/**
* Gets a token associated with the view that can be used to grant the view focus.
*/
- public IBinder getFocusGrantToken(View view) {
+ public InputTransferToken getFocusGrantToken(View view) {
SurfaceControlViewHost root = mViewRoots.get(view);
if (root == null) {
Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index aac1d0626d30..11e477716eb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -91,8 +91,9 @@ abstract class MagnetizedObject<T : Any>(
* to [onUnstuckFromTarget] or [onReleasedInTarget].
*
* @param target The target that the object is now stuck to.
+ * @param draggedObject The object that is stuck to the target.
*/
- fun onStuckToTarget(target: MagneticTarget)
+ fun onStuckToTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>)
/**
* Called when the object is no longer stuck to a target. This means that either touch
@@ -110,6 +111,7 @@ abstract class MagnetizedObject<T : Any>(
* and [maybeConsumeMotionEvent] is now returning false.
*
* @param target The target that this object was just unstuck from.
+ * @param draggedObject The object being unstuck from the target.
* @param velX The X velocity of the touch gesture when it exited the magnetic field.
* @param velY The Y velocity of the touch gesture when it exited the magnetic field.
* @param wasFlungOut Whether the object was unstuck via a fling gesture. This means that
@@ -119,6 +121,7 @@ abstract class MagnetizedObject<T : Any>(
*/
fun onUnstuckFromTarget(
target: MagneticTarget,
+ draggedObject: MagnetizedObject<*>,
velX: Float,
velY: Float,
wasFlungOut: Boolean
@@ -129,8 +132,9 @@ abstract class MagnetizedObject<T : Any>(
* velocity to reach it.
*
* @param target The target that the object was released in.
+ * @param draggedObject The object released in the target.
*/
- fun onReleasedInTarget(target: MagneticTarget)
+ fun onReleasedInTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>)
}
private val animator: PhysicsAnimator<T> = PhysicsAnimator.getInstance(underlyingObject)
@@ -352,8 +356,8 @@ abstract class MagnetizedObject<T : Any>(
val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
val distanceFromTargetCenter = hypot(
- ev.rawX - target.centerOnScreen.x,
- ev.rawY - target.centerOnScreen.y)
+ ev.rawX - target.centerOnDisplayX(),
+ ev.rawY - target.centerOnDisplayY())
distanceFromTargetCenter < target.magneticFieldRadiusPx
}
@@ -386,7 +390,7 @@ abstract class MagnetizedObject<T : Any>(
// animate sticking to the magnet.
targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
cancelAnimations()
- magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
+ magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!, this)
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
@@ -397,7 +401,8 @@ abstract class MagnetizedObject<T : Any>(
// move the object out of the target using its own movement logic.
cancelAnimations()
magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velocityTracker.xVelocity, velocityTracker.yVelocity,
+ targetObjectIsStuckTo!!, this,
+ velocityTracker.xVelocity, velocityTracker.yVelocity,
wasFlungOut = false)
targetObjectIsStuckTo = null
@@ -406,7 +411,6 @@ abstract class MagnetizedObject<T : Any>(
// First, check for relevant gestures concluding with an ACTION_UP.
if (ev.action == MotionEvent.ACTION_UP) {
-
velocityTracker.computeCurrentVelocity(1000 /* units */)
val velX = velocityTracker.xVelocity
val velY = velocityTracker.yVelocity
@@ -421,10 +425,11 @@ abstract class MagnetizedObject<T : Any>(
// the upward direction, tell the listener so the object can be animated out of
// the target.
magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velX, velY, wasFlungOut = true)
+ targetObjectIsStuckTo!!, this,
+ velX, velY, wasFlungOut = true)
} else {
// If the object is stuck and not flung away, it was released inside the target.
- magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!)
+ magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!, this)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
@@ -441,11 +446,11 @@ abstract class MagnetizedObject<T : Any>(
if (flungToTarget != null) {
// If this is a fling-to-target, animate the object to the magnet and then release
// it.
- magnetListener.onStuckToTarget(flungToTarget)
+ magnetListener.onStuckToTarget(flungToTarget, this)
targetObjectIsStuckTo = flungToTarget
animateStuckToTarget(flungToTarget, velX, velY, true) {
- magnetListener.onReleasedInTarget(flungToTarget)
+ magnetListener.onReleasedInTarget(flungToTarget, this)
targetObjectIsStuckTo = null
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
@@ -542,7 +547,7 @@ abstract class MagnetizedObject<T : Any>(
// Whether velocity is sufficient, depending on whether we're flinging into a target at the
// top or the bottom of the screen.
val velocitySufficient =
- if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
+ if (rawY < target.centerOnDisplayY()) velY > flingToTargetMinVelocity
else velY < flingToTargetMinVelocity
if (!velocitySufficient) {
@@ -560,15 +565,15 @@ abstract class MagnetizedObject<T : Any>(
val yIntercept = rawY - slope * rawX
// ...calculate the x value when y = the target's y-coordinate.
- targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
+ targetCenterXIntercept = (target.centerOnDisplayY() - yIntercept) / slope
}
// The width of the area we're looking for a fling towards.
val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
// Velocity was sufficient, so return true if the intercept is within the target area.
- return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
- targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
+ return targetCenterXIntercept > target.centerOnDisplayX() - targetAreaWidth / 2 &&
+ targetCenterXIntercept < target.centerOnDisplayX() + targetAreaWidth / 2
}
/** Cancel animations on this object's x/y properties. */
@@ -601,6 +606,22 @@ abstract class MagnetizedObject<T : Any>(
) {
val centerOnScreen = PointF()
+ /**
+ * Set screen vertical offset amount.
+ *
+ * Screen surface may be vertically shifted in some cases, for example when one-handed mode
+ * is enabled. [MagneticTarget] and [MagnetizedObject] set their location in screen
+ * coordinates (see [MagneticTarget.centerOnScreen] and
+ * [MagnetizedObject.getLocationOnScreen] respectively).
+ *
+ * When a [MagnetizedObject] is dragged, the touch location is determined by
+ * [MotionEvent.getRawX] and [MotionEvent.getRawY]. These work in display coordinates. When
+ * screen is shifted due to one-handed mode, display coordinates and screen coordinates do
+ * not match. To determine if a [MagnetizedObject] is dragged into a [MagneticTarget], view
+ * location on screen is translated to display coordinates using this offset value.
+ */
+ var screenVerticalOffset: Int = 0
+
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
@@ -614,6 +635,23 @@ abstract class MagnetizedObject<T : Any>(
tempLoc[1] + targetView.height / 2f - targetView.translationY)
}
}
+
+ /**
+ * Get target center coordinate on x-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first.
+ */
+ fun centerOnDisplayX(): Float {
+ return centerOnScreen.x
+ }
+
+ /**
+ * Get target center coordinate on y-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first. Use [screenVerticalOffset] to update the
+ * screen offset compared to the display.
+ */
+ fun centerOnDisplayY(): Float {
+ return centerOnScreen.y + screenVerticalOffset
+ }
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index 3906599b7581..b5f25433f9aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.app.PictureInPictureParams;
import android.view.SurfaceControl;
@@ -22,7 +22,7 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
/**
* Interface that is exposed to remote callers to manipulate the Pip feature.
@@ -52,9 +52,10 @@ interface IPip {
* @param componentName ComponentName represents the Activity
* @param destinationBounds the destination bounds the PiP window lands into
* @param overlay an optional overlay to fade out after entering PiP
+ * @param appBounds the bounds used to set the buffer size of the optional content overlay
*/
oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
- in Rect destinationBounds, in SurfaceControl overlay) = 2;
+ in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
/**
* Notifies the swiping Activity to PiP onto home transition is aborted
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl
index 062e3ba26356..b8d1966100a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
/**
* Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index a9f687fc9b2d..6ffeb97f50fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -125,11 +125,15 @@ public class PipBoundsAlgorithm {
public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
- final Rect destinationBounds = reentryState != null
- ? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize())
- : getDefaultBounds();
+ final Rect destinationBounds = getDefaultBounds();
+ if (reentryState != null) {
+ final Size scaledBounds = new Size(
+ Math.round(mPipBoundsState.getMaxSize().x * reentryState.getBoundsScale()),
+ Math.round(mPipBoundsState.getMaxSize().y * reentryState.getBoundsScale()));
+ destinationBounds.set(getDefaultBounds(reentryState.getSnapFraction(), scaledBounds));
+ }
- final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
+ final boolean useCurrentSize = reentryState != null;
Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
useCurrentSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 8b6c7b663f82..b87c2f6ebad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -140,7 +140,7 @@ public class PipBoundsState {
// spec takes the aspect ratio of the bounds into account, so both width and height
// scale by the same factor.
addPipExclusionBoundsChangeCallback((bounds) -> {
- mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+ updateBoundsScale();
});
}
@@ -152,6 +152,11 @@ public class PipBoundsState {
mSizeSpecSource.onConfigurationChanged();
}
+ /** Update the bounds scale percentage value. */
+ public void updateBoundsScale() {
+ mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f);
+ }
+
private void reloadResources() {
mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
}
@@ -223,6 +228,14 @@ public class PipBoundsState {
mExpandedMovementBounds.set(bounds);
}
+ /** Updates the min and max sizes based on the size spec and aspect ratio. */
+ public void updateMinMaxSize(float aspectRatio) {
+ final Size minSize = mSizeSpecSource.getMinSize(aspectRatio);
+ mMinSize.set(minSize.getWidth(), minSize.getHeight());
+ final Size maxSize = mSizeSpecSource.getMaxSize(aspectRatio);
+ mMaxSize.set(maxSize.getWidth(), maxSize.getHeight());
+ }
+
/** Sets the max possible size for resize. */
public void setMaxSize(int width, int height) {
mMaxSize.set(width, height);
@@ -255,7 +268,7 @@ public class PipBoundsState {
mStashedState = stashedState;
try {
- ActivityTaskManager.getService().onPictureInPictureStateChanged(
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
);
} catch (RemoteException | IllegalStateException e) {
@@ -293,8 +306,8 @@ public class PipBoundsState {
}
/** Save the reentry state to restore to when re-entering PIP mode. */
- public void saveReentryState(Size size, float fraction) {
- mPipReentryState = new PipReentryState(size, fraction);
+ public void saveReentryState(float fraction) {
+ mPipReentryState = new PipReentryState(mBoundsScale, fraction);
}
/** Returns the saved reentry state. */
@@ -596,17 +609,16 @@ public class PipBoundsState {
public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
- private final @Nullable Size mSize;
private final float mSnapFraction;
+ private final float mBoundsScale;
- PipReentryState(@Nullable Size size, float snapFraction) {
- mSize = size;
+ PipReentryState(float boundsScale, float snapFraction) {
+ mBoundsScale = boundsScale;
mSnapFraction = snapFraction;
}
- @Nullable
- public Size getSize() {
- return mSize;
+ public float getBoundsScale() {
+ return mBoundsScale;
}
public float getSnapFraction() {
@@ -616,7 +628,7 @@ public class PipBoundsState {
void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSize=" + mSize);
+ pw.println(innerPrefix + "mBoundsScale=" + mBoundsScale);
pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 1b1ebc39b558..4cbb78f2dae2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.common.pip.PipBoundsState;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,9 +48,9 @@ public class PipDoubleTapHelper {
@Retention(RetentionPolicy.SOURCE)
@interface PipSizeSpec {}
- static final int SIZE_SPEC_DEFAULT = 0;
- static final int SIZE_SPEC_MAX = 1;
- static final int SIZE_SPEC_CUSTOM = 2;
+ public static final int SIZE_SPEC_DEFAULT = 0;
+ public static final int SIZE_SPEC_MAX = 1;
+ public static final int SIZE_SPEC_CUSTOM = 2;
/**
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
@@ -84,7 +82,7 @@ public class PipDoubleTapHelper {
* @return pip screen size to switch to
*/
@PipSizeSpec
- static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
@NonNull Rect userResizeBounds) {
// is pip screen at its maximum
boolean isScreenMax = mPipBoundsState.getBounds().width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 0775f5279e31..85353d307056 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -33,12 +33,13 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
import java.util.List;
/**
- * Interface to allow {@link com.android.wm.shell.pip.PipTaskOrganizer} to call into
- * PiP menu when certain events happen (task appear/vanish, PiP move, etc.)
+ * Interface to interact with PiP menu when certain events happen
+ * (task appear/vanish, PiP move, etc.).
*/
public interface PipMenuController {
@@ -52,15 +53,15 @@ public interface PipMenuController {
float ALPHA_NO_CHANGE = -1f;
/**
- * Called when
- * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
+ * Called when out implementation of
+ * {@link ShellTaskOrganizer.TaskListener#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
* is called.
*/
void attach(SurfaceControl leash);
/**
- * Called when
- * {@link PipTaskOrganizer#onTaskVanished(RunningTaskInfo)} is called.
+ * Called when our implementation of
+ * {@link ShellTaskOrganizer.TaskListener#onTaskVanished(RunningTaskInfo)} is called.
*/
void detach();
@@ -101,11 +102,6 @@ public interface PipMenuController {
default void updateMenuBounds(Rect destinationBounds) {}
/**
- * Update when the current focused task changes.
- */
- default void onFocusTaskChanged(RunningTaskInfo taskInfo) {}
-
- /**
* Returns a default LayoutParams for the PIP Menu.
* @param context the context.
* @param width the PIP stack width.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
new file mode 100644
index 000000000000..317e48e19c13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.window.SystemPerformanceHinter.HINT_SF;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.window.SystemPerformanceHinter;
+import android.window.SystemPerformanceHinter.HighPerfSession;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Consumer;
+
+/**
+ * Manages system performance hints for PiP CUJs and interactions.
+ */
+public class PipPerfHintController {
+ private static final String TAG = PipPerfHintController.class.getSimpleName();
+
+ // Delay until signal about a session cleanup is sent.
+ private static final int SESSION_TIMEOUT_DELAY = 20_000;
+
+ // Maximum number of possible high perf session.
+ private static final int SESSION_POOL_SIZE = 20;
+
+ private final SystemPerformanceHinter mSystemPerformanceHinter;
+ @NonNull
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ @NonNull
+ private final ShellExecutor mMainExecutor;
+
+
+ public PipPerfHintController(@NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @NonNull SystemPerformanceHinter systemPerformanceHinter) {
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mMainExecutor = mainExecutor;
+ mSystemPerformanceHinter = systemPerformanceHinter;
+ }
+
+ /**
+ * Starts a high perf session.
+ *
+ * @param timeoutCallback an optional callback to be executed upon session timeout.
+ * @return a wrapper around the session to allow for early closing; null if no free sessions
+ * left available in the pool.
+ */
+ @Nullable
+ public PipHighPerfSession startSession(@Nullable Consumer<PipHighPerfSession> timeoutCallback,
+ String reason) {
+ if (PipHighPerfSession.getActiveSessionsCount() == SESSION_POOL_SIZE) {
+ return null;
+ }
+
+ HighPerfSession highPerfSession = mSystemPerformanceHinter.startSession(HINT_SF,
+ mPipDisplayLayoutState.getDisplayId(), "pip-high-perf-session");
+ PipHighPerfSession pipHighPerfSession = new PipHighPerfSession(highPerfSession, reason);
+
+ if (timeoutCallback != null) {
+ mMainExecutor.executeDelayed(() -> {
+ if (PipHighPerfSession.hasClosedOrFinalized(pipHighPerfSession)) {
+ // If the session is either directly closed or GC collected before timeout
+ // was reached, do not send the timeout callback.
+ return;
+ }
+ // The session hasn't been closed yet, so do that now, along with any cleanup.
+ pipHighPerfSession.close();
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s timed out", TAG,
+ pipHighPerfSession.toString());
+ timeoutCallback.accept(pipHighPerfSession);
+ }, SESSION_TIMEOUT_DELAY);
+ }
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s is started",
+ TAG, pipHighPerfSession.toString());
+ return pipHighPerfSession;
+ }
+
+ /**
+ * Dumps the inner state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "activeSessionCount="
+ + PipHighPerfSession.getActiveSessionsCount());
+ }
+
+ /**
+ * A wrapper around {@link HighPerfSession} to keep track of some extra metadata about
+ * the session's status.
+ */
+ public class PipHighPerfSession implements AutoCloseable{
+
+ // THe actual HighPerfSession we wrap around.
+ private final HighPerfSession mSession;
+
+ private final String mReason;
+
+ /**
+ * Keeps track of all active sessions using weakly referenced keys.
+ * This makes sure that that sessions do not get accidentally leaked if not closed.
+ */
+ private static Map<PipHighPerfSession, Boolean> sActiveSessions = new WeakHashMap<>();
+
+ private PipHighPerfSession(HighPerfSession session, String reason) {
+ mSession = session;
+ mReason = reason;
+ sActiveSessions.put(this, true);
+ }
+
+ /**
+ * Closes a high perf session.
+ */
+ @Override
+ public void close() {
+ sActiveSessions.remove(this);
+ mSession.close();
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: high perf session %s is closed",
+ TAG, toString());
+ }
+
+ @Override
+ public void finalize() {
+ // The entry should be removed from the weak hash map as well by default.
+ mSession.close();
+ }
+
+ @Override
+ public String toString() {
+ return "[" + super.toString() + "] initially started due to: " + mReason;
+ }
+
+ private static boolean hasClosedOrFinalized(PipHighPerfSession pipHighPerfSession) {
+ return !sActiveSessions.containsKey(pipHighPerfSession);
+ }
+
+ private static int getActiveSessionsCount() {
+ return sActiveSessions.size();
+ }
+ }
+}
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 f801b0d01084..a87116ea4670 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
@@ -75,7 +75,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
private DividerRoundedCorner mCorners;
- private View mBackground;
private int mTouchElevation;
private VelocityTracker mVelocityTracker;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
index 7237d2bde39f..37ccd15587e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -1,2 +1,4 @@
# WM shell sub-modules splitscreen owner
chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 2b1037711249..dae62ac74483 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -138,8 +138,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
mViewHost.setView(rootLayout, lp);
}
- /** Releases the surfaces for split decor. */
- public void release(SurfaceControl.Transaction t) {
+ /**
+ * Cancels any currently running animations.
+ */
+ public void cancelRunningAnimations() {
if (mFadeAnimator != null) {
if (mFadeAnimator.isRunning()) {
mFadeAnimator.cancel();
@@ -152,6 +154,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
mScreenshotAnimator = null;
}
+ }
+
+ /** Releases the surfaces for split decor. */
+ public void release(SurfaceControl.Transaction t) {
+ cancelRunningAnimations();
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -277,7 +284,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
@Override
- public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+ public void onAnimationEnd(@NonNull Animator animation) {
mRunningAnimationCount--;
animT.remove(mScreenshot);
animT.apply();
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 53caddb52f23..6b2d544c192a 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
@@ -510,16 +510,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
- /** Updates divide position and split bounds base on the ratio within root bounds. */
+ /**
+ * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+ * to middle position if the provided SnapTarget is not supported.
+ */
public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
snapPosition);
- if (snapTarget == null) {
- throw new IllegalArgumentException("No SnapTarget for position " + snapPosition);
- }
-
- setDividePosition(snapTarget.position, false /* applyLayoutChange */);
+ setDividePosition(snapTarget != null
+ ? snapTarget.position
+ : mDividerSnapAlgorithm.getMiddleTarget().position,
+ false /* applyLayoutChange */);
}
/** Resets divider position. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 49db8d9c54a6..e8c809e5db4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -20,10 +20,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
+import com.android.wm.shell.shared.TransitionUtil;
+
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
@@ -126,7 +127,7 @@ public class SplitScreenConstants {
WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0693543515b4..f9259e79472e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +24,27 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
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 android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -91,12 +99,6 @@ public class SplitScreenUtils {
return taskInfo != null ? taskInfo.userId : -1;
}
- /** Returns true if package names and user ids match. */
- public static boolean samePackage(String packageName1, String packageName2,
- int userId1, int userId2) {
- return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
- }
-
/** Generates a common log message for split screen failures */
public static String splitFailureMessage(String caller, String reason) {
return "(" + caller + ") Splitscreen aborted: " + reason;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b204bdb..fa2e23647a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
"has_seen_vertical_reachability_education";
+ private static final int MAX_PERCENTAGE_VAL = 100;
+
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
* education.
@@ -82,6 +84,12 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
*/
private final SharedPreferences mLetterboxEduSharedPreferences;
+ /**
+ * The minimum tolerance of the percentage of activity bounds within its task to hide
+ * size compat restart button.
+ */
+ private final int mHideSizeCompatRestartButtonTolerance;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -106,6 +114,9 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
R.bool.config_letterboxIsRestartDialogEnabled);
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
+ final int tolerance = context.getResources().getInteger(
+ R.integer.config_letterboxRestartButtonHideTolerance);
+ mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
|| !hasSeenVerticalReachabilityEducation(taskInfo));
}
+ int getHideSizeCompatRestartButtonTolerance() {
+ return mHideSizeCompatRestartButtonTolerance;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
}
}
+ // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+ // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ // 100 is the default value where the activity has to fit exactly within the task to allow
+ // size compat restart button to be hidden. 0 means size compat restart button will always
+ // be hidden.
+ private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+ return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+ }
+
private boolean isReachabilityEducationEnabled() {
return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
&& mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb034b6..dbf7186def8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPL
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -33,6 +35,7 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@VisibleForTesting
CompatUILayout mLayout;
+ private final float mHideScmTolerance;
+
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -80,6 +85,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
+ mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
}
@Override
@@ -99,7 +105,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected boolean eligibleToShowLayout() {
- return mHasSizeCompat || shouldShowCameraControl();
+ return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+ || shouldShowCameraControl();
}
@Override
@@ -208,6 +215,30 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
updateSurfacePosition(positionX, positionY);
}
+ @VisibleForTesting
+ boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+ if (!Flags.allowHideScmButton()) {
+ return true;
+ }
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+ if (letterboxArea == 0 || taskArea == 0) {
+ return false;
+ }
+ final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+ return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+ }
+
+ private int computeArea(int width, int height) {
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return width * height;
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c50c78..0564c95aef5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
updateSurfacePosition();
}
- @Nullable
+ @NonNull
protected TaskInfo getLastTaskInfo() {
return mTaskInfo;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index afd3b14e8b1d..7c280994042b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -20,6 +20,7 @@ import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
@@ -88,7 +89,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
mShellExecutor = shellExecutor;
mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
mCompatUIHintsState = compatUIHintsState;
mOnButtonClicked = onButtonClicked;
mDisappearTimeSupplier = disappearTimeSupplier;
@@ -134,7 +136,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
@@ -227,11 +230,21 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
return SystemClock.uptimeMillis() + hideDelay;
}
- private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
- final Intent intent = taskInfo.baseIntent;
- return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
- || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+ private boolean shouldShowUserAspectRatioSettingsButton(@NonNull AppCompatTaskInfo taskInfo,
+ @NonNull Intent intent) {
+ final Rect stableBounds = getTaskStableBounds();
+ final int letterboxHeight = taskInfo.topActivityLetterboxHeight;
+ final int letterboxWidth = taskInfo.topActivityLetterboxWidth;
+ // App is not visibly letterboxed if it covers status bar/bottom insets or matches the
+ // stable bounds, so don't show the button
+ if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) {
+ return false;
+ }
+
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled)
+ && !taskInfo.isSystemFullscreenOverrideEnabled
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index b52a118c7f1e..216da070754b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -26,6 +26,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -83,19 +84,19 @@ public class TvWMShellModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
- displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
- mainHandler, systemWindows);
+ displayImeController, displayInsetsController, transitions, transactionPool,
+ iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper,
+ mainExecutor, mainHandler, systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3c6bc1754c5c..8b2ec0a35685 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
@@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -66,6 +67,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -78,7 +80,6 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -203,20 +204,6 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DragAndDropController> provideDragAndDropController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
- shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
- }
-
- @WMSingleton
- @Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
Context context,
ShellInit shellInit,
@@ -336,6 +323,12 @@ public abstract class WMShellBaseModule {
return Optional.of(perfHintController.getHinter());
}
+ @WMSingleton
+ @Provides
+ static MultiInstanceHelper provideMultiInstanceHelper(Context context) {
+ return new MultiInstanceHelper(context, context.getPackageManager());
+ }
+
//
// Back animation
//
@@ -363,7 +356,8 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
- Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -374,7 +368,8 @@ public abstract class WMShellBaseModule {
backgroundHandler,
context,
backAnimationBackground,
- animations));
+ animations,
+ shellCommandHandler));
}
return Optional.empty();
}
@@ -409,6 +404,20 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static Optional<PipPerfHintController> providePipPerfHintController(
+ PipDisplayLayoutState pipDisplayLayoutState,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<SystemPerformanceHinter> systemPerformanceHinterOptional) {
+ if (systemPerformanceHinterOptional.isPresent()) {
+ return Optional.of(new PipPerfHintController(pipDisplayLayoutState, mainExecutor,
+ systemPerformanceHinterOptional.get()));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsState providePipBoundsState(Context context,
SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
@@ -909,7 +918,6 @@ public abstract class WMShellBaseModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropControllerOptional,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
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 71bf487249fb..fb3c35b6a1e3 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
@@ -47,6 +47,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -64,6 +65,7 @@ import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -172,7 +174,7 @@ public abstract class WMShellModule {
BubblePositioner positioner,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -199,9 +201,11 @@ public abstract class WMShellModule {
@Provides
static WindowDecorViewModel provideWindowDecorViewModel(
Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellInit shellInit,
+ IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
@@ -210,15 +214,16 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
+ mainExecutor,
mainHandler,
mainChoreographer,
shellInit,
shellCommandHandler,
+ windowManager,
taskOrganizer,
displayController,
shellController,
@@ -226,7 +231,6 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
@@ -235,7 +239,8 @@ public abstract class WMShellModule {
mainChoreographer,
taskOrganizer,
displayController,
- syncQueue);
+ syncQueue,
+ transitions);
}
//
@@ -339,7 +344,7 @@ public abstract class WMShellModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -347,12 +352,14 @@ public abstract class WMShellModule {
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
transactionPool, iconProvider, recentTasks, launchAdjacentController,
- windowDecorViewModel, desktopTasksController, mainExecutor);
+ windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
+ multiInstanceHelper, mainExecutor);
}
//
@@ -495,6 +502,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ DragAndDropController dragAndDropController,
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@@ -503,14 +511,15 @@ public abstract class WMShellModule {
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler,
- desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler,
- mainExecutor);
+ dragAndDropController, transitions, enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@WMSingleton
@@ -554,6 +563,35 @@ public abstract class WMShellModule {
}
//
+ // Drag and drop
+ //
+
+ @WMSingleton
+ @Provides
+ static GlobalDragListener provideGlobalDragListener(
+ IWindowManager wmService,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new GlobalDragListener(wmService, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+ displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
+ mainExecutor);
+ }
+
+ //
// Misc
//
@@ -563,6 +601,7 @@ public abstract class WMShellModule {
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
+ DragAndDropController dragAndDropController,
DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index ba882c403aed..1e3d7fb06da2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -36,6 +36,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
@@ -124,12 +125,11 @@ public abstract class Pip1Module {
static PhonePipMenuController providesPipPhoneMenuController(Context context,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
SystemWindows systemWindows,
- Optional<SplitScreenController> splitScreenOptional,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
+ systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
}
@WMSingleton
@@ -144,10 +144,12 @@ public abstract class Pip1Module {
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
- floatingContentCoordinator, pipUiEventLogger, mainExecutor);
+ floatingContentCoordinator, pipUiEventLogger, mainExecutor,
+ pipPerfHintControllerOptional);
}
@WMSingleton
@@ -170,6 +172,7 @@ public abstract class Pip1Module {
PipTransitionController pipTransitionController,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -177,8 +180,8 @@ public abstract class Pip1Module {
syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, displayController, pipUiEventLogger,
- shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@WMSingleton
@@ -210,10 +213,11 @@ public abstract class Pip1Module {
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
- floatingContentCoordinator);
+ floatingContentCoordinator, pipPerfHintControllerOptional);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3b48c67a5bbd..458ea05e620d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -18,18 +18,23 @@ package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
@@ -50,15 +55,16 @@ import java.util.Optional;
public abstract class Pip2Module {
@WMSingleton
@Provides
- static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ static PipTransition providePipTransition(Context context,
+ @NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
@NonNull PipScheduler pipScheduler) {
- return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm, pipScheduler);
+ return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
}
@WMSingleton
@@ -68,13 +74,18 @@ public abstract class Pip2Module {
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ @ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
} else {
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
- pipDisplayLayoutState));
+ pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
+ mainExecutor));
}
}
@@ -85,4 +96,16 @@ public abstract class Pip2Module {
@ShellMainThread ShellExecutor mainExecutor) {
return new PipScheduler(context, pipBoundsState, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PhonePipMenuController providePipPhoneMenuController(Context context,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ SystemWindows systemWindows,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
+ systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 1947097c2f15..54c2aeab4976 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -34,6 +34,7 @@ import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.dagger.WMShellBaseModule;
@@ -212,6 +213,7 @@ public abstract class TvPipModule {
PipParamsChangedForwarder pipParamsChangedForwarder,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -219,8 +221,8 @@ public abstract class TvPipModule {
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenControllerOptional, displayController, pipUiEventLogger,
- shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index e732a0354806..1071d728a56d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -47,4 +47,10 @@ public interface DesktopMode {
default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
Executor callbackExecutor) { }
+
+ /** Called when requested to go to desktop mode from the current focused app. */
+ void enterDesktop(int displayId);
+
+ /** Called when requested to go to fullscreen from the current focused desktop app. */
+ void moveFocusedTaskToFullscreen(int displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
new file mode 100644
index 000000000000..95d47146e834
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import com.android.internal.util.FrameworkStatsLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Event logger for logging desktop mode session events
+ */
+class DesktopModeEventLogger {
+ /**
+ * Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for
+ * entering desktop mode
+ */
+ fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session enter, session: %s reason: %s",
+ sessionId, enterReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ /* enterReason */ enterReason.reason,
+ /* exitReason */ 0,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs the exit of desktop mode having session id [sessionId] and the reason [exitReason] for
+ * exiting desktop mode
+ */
+ fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session exit, session: %s reason: %s",
+ sessionId, exitReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enterReason */ 0,
+ /* exitReason */ exitReason.reason,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was added in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task added, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was removed in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task remove, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] had it's info changed in the desktop mode session
+ * having session id [sessionId]
+ */
+ fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ companion object {
+ data class TaskUpdate(
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int = Int.MIN_VALUE,
+ val taskWidth: Int = Int.MIN_VALUE,
+ val taskX: Int = Int.MIN_VALUE,
+ val taskY: Int = Int.MIN_VALUE,
+ )
+
+ /**
+ * Enum EnterReason mapped to the EnterReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class EnterReason(val reason: Int) {
+ UNKNOWN_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER
+ ),
+ OVERVIEW(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__OVERVIEW
+ ),
+ APP_HANDLE_DRAG(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_DRAG
+ ),
+ APP_HANDLE_MENU_BUTTON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_MENU_BUTTON
+ ),
+ APP_FREEFORM_INTENT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_FREEFORM_INTENT
+ ),
+ KEYBOARD_SHORTCUT_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER
+ ),
+ SCREEN_ON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON
+ );
+ }
+
+ /**
+ * Enum ExitReason mapped to the ExitReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ExitReason(val reason: Int) {
+ UNKNOWN_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT
+ ),
+ DRAG_TO_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT
+ ),
+ APP_HANDLE_MENU_BUTTON_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__APP_HANDLE_MENU_BUTTON_EXIT
+ ),
+ KEYBOARD_SHORTCUT_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__KEYBOARD_SHORTCUT_EXIT
+ ),
+ RETURN_HOME_OR_OVERVIEW(
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME
+ ),
+ TASK_FINISHED(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED
+ ),
+ SCREEN_OFF(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF
+ )
+ }
+
+ private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
+ private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 000000000000..e1e41ee1e64d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktop(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index dc82fc1b35dd..22ba70860587 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -18,21 +18,13 @@ package com.android.wm.shell.desktopmode;
import android.os.SystemProperties;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
- private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-
- /**
- * Flag to indicate whether desktop mode proto is available on the device
- */
- private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode_2", false);
-
/**
* Flag to indicate whether task resizing is veiled.
*/
@@ -55,16 +47,30 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_stashing", false);
/**
- * Return {@code true} is desktop windowing proto 2 is enabled
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows", true);
+
+ /**
+ * Flag to indicate whether to apply shadows to the focused window in desktop mode.
+ *
+ * Note: this flag is only relevant if USE_WINDOW_SHADOWS is false.
+ */
+ private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
+
+ /**
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_rounded_corners", true);
+
+ /**
+ * Return {@code true} if desktop windowing is enabled
*/
public static boolean isEnabled() {
- // Check for aconfig flag first
- if (ENABLE_DESKTOP_WINDOWING) {
- return true;
- }
- // Fall back to sysprop flag
- // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
- return IS_PROTO2_ENABLED;
+ return Flags.enableDesktopWindowingMode();
}
/**
@@ -81,4 +87,21 @@ public class DesktopModeStatus {
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
+
+ /**
+ * Return whether to use window shadows.
+ *
+ * @param isFocusedWindow whether the window to apply shadows to is focused
+ */
+ public static boolean useWindowShadow(boolean isFocusedWindow) {
+ return USE_WINDOW_SHADOWS
+ || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
+ }
+
+ /**
+ * Return whether to use rounded corners for windows.
+ */
+ public static boolean useRoundedCorners() {
+ return USE_ROUNDED_CORNERS;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c0fc02fadd4d..7c8fcbb16711 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -86,10 +86,10 @@ class DesktopModeTaskRepository {
) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
- val visibleTasks = getVisibleTaskCount(displayId)
+ val visibleTasksCount = getVisibleTaskCount(displayId)
val stashed = isStashed(displayId)
executor.execute {
- visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
@@ -222,10 +222,8 @@ class DesktopModeTaskRepository {
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- // Task removed from other display, check if we should notify listeners
- if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
- notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
- }
+ notifyVisibleTaskListeners(otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size)
}
}
}
@@ -248,15 +246,21 @@ class DesktopModeTaskRepository {
)
}
- // Check if count changed and if there was no tasks or this is the first task
- if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
- notifyVisibleTaskListeners(displayId, newCount > 0)
+ // Check if count changed
+ if (prevCount != newCount) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
+ prevCount,
+ newCount
+ )
+ notifyVisibleTaskListeners(displayId, newCount)
}
}
- private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -264,6 +268,11 @@ class DesktopModeTaskRepository {
* Get number of tasks that are marked as visible on given [displayId]
*/
fun getVisibleTaskCount(displayId: Int): Int {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount= %d",
+ displayData[displayId]?.visibleTasks?.size ?: 0
+ )
return displayData[displayId]?.visibleTasks?.size ?: 0
}
@@ -292,6 +301,10 @@ class DesktopModeTaskRepository {
taskId
)
freeformTasksInZOrder.remove(taskId)
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
+ )
}
/**
@@ -379,9 +392,9 @@ class DesktopModeTaskRepository {
*/
interface VisibleTasksListener {
/**
- * Called when the desktop starts or stops showing freeform tasks.
+ * Called when the desktop changes the number of visible freeform tasks.
*/
- fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
/**
* Called when the desktop stashed status changes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index a587bed3fef0..fb0ed1587055 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,7 +16,11 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -24,11 +28,13 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Region;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -37,9 +43,10 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -48,99 +55,156 @@ import com.android.wm.shell.common.SyncTransactionQueue;
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
- public static final int INVALID_INDICATOR = -1;
- /** Indicates impending transition into desktop mode */
- public static final int TO_DESKTOP_INDICATOR = 1;
- /** Indicates impending transition into fullscreen */
- public static final int TO_FULLSCREEN_INDICATOR = 2;
- /** Indicates impending transition into split select on the left side */
- public static final int TO_SPLIT_LEFT_INDICATOR = 3;
- /** Indicates impending transition into split select on the right side */
- public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
+ public enum IndicatorType {
+ /** To be used when we don't want to indicate any transition */
+ NO_INDICATOR,
+ /** Indicates impending transition into desktop mode */
+ TO_DESKTOP_INDICATOR,
+ /** Indicates impending transition into fullscreen */
+ TO_FULLSCREEN_INDICATOR,
+ /** Indicates impending transition into split select on the left side */
+ TO_SPLIT_LEFT_INDICATOR,
+ /** Indicates impending transition into split select on the right side */
+ TO_SPLIT_RIGHT_INDICATOR
+ }
private final Context mContext;
private final DisplayController mDisplayController;
- private final ShellTaskOrganizer mTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
- private final Rect mIndicatorRange = new Rect();
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
private SurfaceControlViewHost mViewHost;
private View mView;
- private boolean mIsFullscreen;
- private int mType;
+ private IndicatorType mCurrentType;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
- Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
- RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
+ Context context, SurfaceControl taskSurface,
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
mTaskSurface = taskSurface;
- mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mType = type;
- defineIndicatorRange();
- createView();
+ mCurrentType = IndicatorType.NO_INDICATOR;
}
/**
- * If an indicator is warranted based on the input and task bounds, return the type of
- * indicator that should be created.
+ * Based on the coordinates of the current drag event, determine which indicator type we should
+ * display, including no visible indicator.
*/
- public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
- DisplayLayout layout, Context context) {
- int transitionAreaHeight = context.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = context.getResources().getDimensionPixelSize(
+ @NonNull
+ IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+ IndicatorType result = IndicatorType.NO_INDICATOR;
+ final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
- if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
- if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
- return TO_SPLIT_RIGHT_INDICATOR;
+ // Because drags in freeform use task position for indicator calculation, we need to
+ // account for the possibility of the task going off the top of the screen by captionHeight
+ final int captionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
+ final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
+ captionHeight);
+ final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
+ splitLeftRegion, splitRightRegion, fullscreenRegion);
+ if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+ }
+ if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+ }
+ if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
- return INVALID_INDICATOR;
+ if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_DESKTOP_INDICATOR;
+ }
+ transitionIndicator(result);
+ return result;
}
- /**
- * Determine range of inputs that will keep this indicator displaying.
- */
- private void defineIndicatorRange() {
- DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
- int captionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.freeform_decor_caption_height);
- int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- switch (mType) {
- case TO_DESKTOP_INDICATOR:
- // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
- mIndicatorRange.set(0, 0, layout.width(), layout.height());
- break;
- case TO_FULLSCREEN_INDICATOR:
- // If drag results in caption going above the top edge of the display, we still
- // want to transition to fullscreen.
- mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
- layout.width(), layout.height());
- break;
- default:
- break;
+ @VisibleForTesting
+ Region calculateFullscreenRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
+ final Region region = new Region();
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? 2 * layout.stableInsets().top
+ : mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ // A thin, short Rect at the top of the screen.
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
+ region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
+ -captionHeight,
+ (layout.width() / 2) + (fromFreeformWidth / 2),
+ transitionHeight));
+ }
+ // A screen-wide, shorter Rect if the task is in fullscreen or split.
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ region.union(new Rect(0,
+ -captionHeight,
+ layout.width(),
+ transitionHeight));
+ }
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateToDesktopRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ Region splitLeftRegion, Region splitRightRegion,
+ Region toFullscreenRegion) {
+ final Region region = new Region();
+ // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
+ if (windowingMode != WINDOWING_MODE_FREEFORM) {
+ region.union(new Rect(0, 0, layout.width(), layout.height()));
+ region.op(splitLeftRegion, Region.Op.DIFFERENCE);
+ region.op(splitRightRegion, Region.Op.DIFFERENCE);
+ region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
}
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateSplitLeftRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height()));
+ return region;
}
+ @VisibleForTesting
+ Region calculateSplitRightRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight,
+ layout.width(), layout.height()));
+ return region;
+ }
/**
* Create a fullscreen indicator with no animation
@@ -155,34 +219,15 @@ public class DesktopModeVisualIndicator {
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
- String description;
- switch (mType) {
- case TO_DESKTOP_INDICATOR:
- description = "Desktop indicator";
- break;
- case TO_FULLSCREEN_INDICATOR:
- description = "Fullscreen indicator";
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- description = "Split Left indicator";
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- description = "Split Right indicator";
- break;
- default:
- description = "Invalid indicator";
- break;
- }
mLeash = builder
- .setName(description)
+ .setName("Desktop Mode Visual Indicator")
.setContainerLayer()
.build();
t.show(mLeash);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(screenWidth, screenHeight,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
+ new WindowManager.LayoutParams(screenWidth, screenHeight, TYPE_APPLICATION,
+ FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Desktop Mode Visual Indicator");
lp.setTrustedOverlay();
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
@@ -201,46 +246,47 @@ public class DesktopModeVisualIndicator {
}
/**
- * Create an indicator. Animator fades it in while expanding the bounds outwards.
+ * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
*/
- public void createIndicatorWithAnimatedBounds() {
- mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
+ private void fadeInIndicator(IndicatorType type) {
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .animateBounds(mView, mType,
+ .fadeBoundsIn(mView, type,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = type;
}
/**
- * Takes existing fullscreen indicator and animates it to freeform bounds
+ * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
*/
- public void transitionFullscreenIndicatorToFreeform() {
- mIsFullscreen = false;
- mType = TO_DESKTOP_INDICATOR;
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
- animator.start();
- }
-
- /**
- * Takes the existing freeform indicator and animates it to fullscreen
- */
- public void transitionFreeformIndicatorToFullscreen() {
- mIsFullscreen = true;
- mType = TO_FULLSCREEN_INDICATOR;
- final VisualIndicatorAnimator animator =
- VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ private void fadeOutIndicator() {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+ .fadeBoundsOut(mView, mCurrentType,
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = IndicatorType.NO_INDICATOR;
}
/**
- * Determine if a MotionEvent is in the same range that enabled the indicator.
- * Used to dismiss the indicator when a transition will no longer result from releasing.
+ * Takes existing indicator and animates it to bounds reflecting a new indicator type.
*/
- public boolean eventOutsideRange(float x, float y) {
- return !mIndicatorRange.contains((int) x, (int) y);
+ private void transitionIndicator(IndicatorType newType) {
+ if (mCurrentType == newType) return;
+ if (mView == null) {
+ createView();
+ }
+ if (mCurrentType == IndicatorType.NO_INDICATOR) {
+ fadeInIndicator(newType);
+ } else if (newType == IndicatorType.NO_INDICATOR) {
+ fadeOutIndicator();
+ } else {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+ newType);
+ mCurrentType = newType;
+ animator.start();
+ }
}
/**
@@ -260,13 +306,6 @@ public class DesktopModeVisualIndicator {
}
/**
- * Returns true if visual indicator is fullscreen
- */
- public boolean isFullscreen() {
- return mIsFullscreen;
- }
-
- /**
* Animator for Desktop Mode transitions which supports bounds and alpha animation.
*/
private static class VisualIndicatorAnimator extends ValueAnimator {
@@ -274,6 +313,13 @@ public class DesktopModeVisualIndicator {
private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+ /** Determines how this animator will interact with the view's alpha:
+ * Fade in, fade out, or no change to alpha
+ */
+ private enum AlphaAnimType{
+ ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
+ }
+
private final View mView;
private final Rect mStartBounds;
private final Rect mEndBounds;
@@ -288,87 +334,92 @@ public class DesktopModeVisualIndicator {
mRectEvaluator = new RectEvaluator(new Rect());
}
- /**
- * Create animator for visual indicator of fullscreen transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
- public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
- @NonNull View view, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect(padding, padding,
- displayLayout.width() - padding, displayLayout.height() - padding);
+ private static VisualIndicatorAnimator fadeBoundsIn(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, type);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
return animator;
}
- public static VisualIndicatorAnimator animateBounds(
- @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect();
- switch (type) {
- case TO_FULLSCREEN_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() / 2 - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- startBounds.set(displayLayout.width() / 2 + padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- }
+ private static VisualIndicatorAnimator fadeBoundsOut(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect endBounds = getIndicatorBounds(displayLayout, type);
+ final Rect startBounds = getMaxBounds(endBounds);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, getMaxBounds(startBounds));
+ view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
return animator;
}
/**
- * Create animator for visual indicator of freeform transition
+ * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+ * freeform to split, etc.)
*
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
+ * @param origType the original indicator type
+ * @param newType the new indicator type
*/
- public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
- @NonNull DisplayLayout displayLayout) {
- final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
- final int width = displayLayout.width();
- final int height = displayLayout.height();
- Rect startBounds = new Rect(0, 0, width, height);
- Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
- (int) (adjustmentPercentage * height / 2),
- (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
- (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+ private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
+ @NonNull DisplayLayout displayLayout, IndicatorType origType,
+ IndicatorType newType) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, origType);
+ final Rect endBounds = getIndicatorBounds(displayLayout, newType);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
return animator;
}
+ private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type) {
+ final int padding = layout.stableInsets().top;
+ switch (type) {
+ case TO_FULLSCREEN_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ case TO_DESKTOP_INDICATOR:
+ final float adjustmentPercentage = 1f
+ - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+ return new Rect((int) (adjustmentPercentage * layout.width() / 2),
+ (int) (adjustmentPercentage * layout.height() / 2),
+ (int) (layout.width() - (adjustmentPercentage * layout.width() / 2)),
+ (int) (layout.height() - (adjustmentPercentage * layout.height() / 2)));
+ case TO_SPLIT_LEFT_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() / 2 - padding,
+ layout.height() - padding);
+ case TO_SPLIT_RIGHT_INDICATOR:
+ return new Rect(layout.width() / 2 + padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ default:
+ throw new IllegalArgumentException("Invalid indicator type provided.");
+ }
+ }
+
/**
* Add necessary listener for animation of indicator
*/
- private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
+ private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
+ AlphaAnimType animType) {
animator.addUpdateListener(a -> {
if (animator.mView != null) {
animator.updateBounds(a.getAnimatedFraction(), animator.mView);
- animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+ animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+ animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
+ }
} else {
animator.cancel();
}
@@ -394,7 +445,7 @@ public class DesktopModeVisualIndicator {
if (mStartBounds.equals(mEndBounds)) {
return;
}
- Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+ final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
view.getBackground().setBounds(currentBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 144555dd70c3..654409f4a637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,20 +17,24 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.content.Intent
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
-import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -45,9 +49,12 @@ import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.MultiInstanceHelper
+import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
@@ -55,15 +62,14 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
-import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
+import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
-import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -71,8 +77,8 @@ import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -87,6 +93,7 @@ class DesktopTasksController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val syncQueue: SyncTransactionQueue,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
@@ -96,19 +103,24 @@ class DesktopTasksController(
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
+ private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor
-) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
+ DragAndDropController.DragAndDropListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
- launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
}
}
private val dragToDesktopStateListener = object : DragToDesktopStateListener {
@@ -128,7 +140,7 @@ class DesktopTasksController(
private val transitionAreaHeight
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
)
private val transitionAreaWidth
@@ -149,6 +161,8 @@ class DesktopTasksController(
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -169,6 +183,13 @@ class DesktopTasksController(
}
}
)
+ dragAndDropController.addListener(this)
+ }
+
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
}
/** Setter needed to avoid cyclic dependency. */
@@ -230,22 +251,57 @@ class DesktopTasksController(
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
}
+ /** Enter desktop by using the focused task in given `displayId` */
+ fun enterDesktop(displayId: Int) {
+ val allFocusedTasks =
+ shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
+ taskInfo.isFocused &&
+ (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ taskInfo.activityType != ACTIVITY_TYPE_HOME
+ }
+ if (allFocusedTasks.isNotEmpty()) {
+ when (allFocusedTasks.size) {
+ 2 -> {
+ // Split-screen case where there are two focused tasks, then we find the child
+ // task to move to desktop.
+ val splitFocusedTask =
+ if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+ allFocusedTasks[1]
+ else allFocusedTasks[0]
+ moveToDesktop(splitFocusedTask)
+ }
+ 1 -> {
+ // Fullscreen case where we move the current focused task.
+ moveToDesktop(allFocusedTasks[0].taskId)
+ }
+ else -> {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop, expected less " +
+ "than 3 focused tasks but found %d",
+ allFocusedTasks.size
+ )
+ }
+ }
+ }
+ }
+
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
taskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction()
- ) {
+ ): Boolean {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- task -> moveToDesktop(decor, task, wct)
- }
+ task -> moveToDesktop(task, wct)
+ } ?: return false
+ return true
}
/**
* Move a task to desktop
*/
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
@@ -260,7 +316,7 @@ class DesktopTasksController(
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -273,7 +329,6 @@ class DesktopTasksController(
fun startDragToDesktop(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
- windowDecor: DesktopModeWindowDecoration
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
@@ -282,8 +337,7 @@ class DesktopTasksController(
)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
- dragToDesktopValueAnimator,
- windowDecor
+ dragToDesktopValueAnimator
)
}
@@ -320,13 +374,24 @@ class DesktopTasksController(
}
/** Move a task with given `taskId` to fullscreen */
- fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+ fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(task, task.positionInParent)
}
}
+ /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
+ fun enterFullscreen(displayId: Int) {
+ if (DesktopModeStatus.isEnabled()) {
+ shellTaskOrganizer
+ .getRunningTasks(displayId)
+ .find { taskInfo ->
+ taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
+ }
+ }
+
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
KtProtoLog.v(
@@ -349,25 +414,11 @@ class DesktopTasksController(
splitScreenController.prepareExitSplitScreen(
wct,
splitScreenController.getStageOfTask(taskInfo.taskId),
- EXIT_REASON_ENTER_DESKTOP
+ EXIT_REASON_DESKTOP_MODE
)
- getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo ->
- wct.removeTask(otherTaskInfo.token)
- }
}
}
- private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? {
- val remainingTaskPosition: Int =
- if (splitScreenController.getSplitPosition(taskId)
- == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
- return splitScreenController.getTaskInfo(remainingTaskPosition)
- }
-
/**
* The second part of the animated drag to desktop transition, called after
* [startDragToDesktop].
@@ -485,7 +536,7 @@ class DesktopTasksController(
}
/** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
- fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect()
@@ -494,11 +545,7 @@ class DesktopTasksController(
if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
// The desktop task is currently occupying the whole stable bounds, toggle to the
// default bounds.
- getDefaultDesktopTaskBounds(
- density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
- stableBounds = stableBounds,
- outBounds = destinationBounds
- )
+ getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
} else {
// Toggle to the stable bounds.
destinationBounds.set(stableBounds)
@@ -506,11 +553,7 @@ class DesktopTasksController(
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -521,59 +564,57 @@ class DesktopTasksController(
*
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
- fun snapToHalfScreen(
- taskInfo: RunningTaskInfo,
- windowDecor: DesktopModeWindowDecoration,
- position: SnapPosition
- ) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
+ val destinationBounds = getSnapBounds(taskInfo, position)
+
+ if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
+
+ val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+ // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
+ val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+ // Update width and height with default desktop mode values
+ val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ outBounds.set(0, 0, desiredWidth, desiredHeight)
+ // Center the task in screen bounds
+ outBounds.offset(
+ screenBounds.centerX() - outBounds.centerX(),
+ screenBounds.centerY() - outBounds.centerY())
+ }
+
+ private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
val stableBounds = Rect()
displayLayout.getStableBounds(stableBounds)
val destinationWidth = stableBounds.width() / 2
- val destinationBounds = when (position) {
+ return when (position) {
SnapPosition.LEFT -> {
Rect(
- stableBounds.left,
- stableBounds.top,
- stableBounds.left + destinationWidth,
- stableBounds.bottom
+ stableBounds.left,
+ stableBounds.top,
+ stableBounds.left + destinationWidth,
+ stableBounds.bottom
)
}
SnapPosition.RIGHT -> {
Rect(
- stableBounds.right - destinationWidth,
- stableBounds.top,
- stableBounds.right,
- stableBounds.bottom
+ stableBounds.right - destinationWidth,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom
)
}
}
-
- if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
-
- val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
- } else {
- shellTaskOrganizer.applyTransaction(wct)
- }
- }
-
- private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
- val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
- val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
- outBounds.set(0, 0, width, height)
- // Center the task in stable bounds
- outBounds.offset(
- stableBounds.centerX() - outBounds.centerX(),
- stableBounds.centerY() - outBounds.centerY()
- )
}
/**
@@ -609,7 +650,7 @@ class DesktopTasksController(
?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
}
- private fun releaseVisualIndicator() {
+ fun releaseVisualIndicator() {
val t = SurfaceControl.Transaction()
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
@@ -721,6 +762,9 @@ class DesktopTasksController(
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
+ if (!DesktopModeStatus.useRoundedCorners()) {
+ return
+ }
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
@@ -869,31 +913,31 @@ class DesktopTasksController(
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
- * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+ * @param inputX x coordinate of input. Used for checks against left/right edge of screen.
* @param taskBounds bounds of dragged task. Used for checks against status bar height.
*/
fun onDragPositioningMove(
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
- inputCoordinate: PointF,
+ inputX: Float,
taskBounds: Rect
) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
- var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
- taskBounds, displayLayout, context)
- if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(
- syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, type)
- visualIndicator?.createIndicatorWithAnimatedBounds()
- return
- }
- if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
- taskBounds.top.toFloat()) == true) {
- releaseVisualIndicator()
- }
+ updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat())
+ }
+
+ fun updateVisualIndicator(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ inputX: Float,
+ taskTop: Float
+ ): DesktopModeVisualIndicator.IndicatorType {
+ // If the visual indicator does not exist, create it.
+ val indicator = visualIndicator ?: DesktopModeVisualIndicator(
+ syncQueue, taskInfo, displayController, context, taskSurface,
+ rootTaskDisplayAreaOrganizer)
+ if (visualIndicator == null) visualIndicator = indicator
+ return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
/**
@@ -903,76 +947,41 @@ class DesktopTasksController(
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
* @param taskBounds the updated bounds of the task being dragged.
- * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect,
- windowDecor: DesktopModeWindowDecoration
+ taskBounds: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
- if (taskBounds.top <= transitionAreaHeight) {
- windowDecor.incrementRelayoutBlock()
- moveToFullscreenWithAnimation(taskInfo, position)
- }
- if (inputCoordinate.x <= transitionAreaWidth) {
- releaseVisualIndicator()
- var wct = WindowContainerTransaction()
- addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct,
- SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
- }
- if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
- ?.minus(transitionAreaWidth) ?: return)) {
- releaseVisualIndicator()
- var wct = WindowContainerTransaction()
- addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct,
- SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
- }
- }
- /**
- * Perform checks required on drag move. Create/release fullscreen indicator and transitions
- * indicator to freeform or fullscreen dimensions as needed.
- *
- * @param taskInfo the task being dragged.
- * @param taskSurface SurfaceControl of dragged task.
- * @param y coordinate of dragged task. Used for checks against status bar height.
- */
- fun onDragPositioningMoveThroughStatusBar(
- taskInfo: RunningTaskInfo,
- taskSurface: SurfaceControl,
- y: Float
- ) {
- // If the motion event is above the status bar and the visual indicator is not yet visible,
- // return since we do not need to show the visual indicator at this point.
- if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
- return
- }
- if (visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
- // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has
- // started, or it'll be visible too early on top of the task surface, especially in
- // the cancel-early case. Also because it shouldn't even be shown in the cancel-early
- // case since its dismissal is tied to the cancel animation end, which doesn't even run
- // in cancel-early.
- visualIndicator?.createIndicatorWithAnimatedBounds()
- }
val indicator = visualIndicator ?: return
- if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
- if (indicator.isFullscreen) {
- indicator.transitionFullscreenIndicatorToFreeform()
+ val indicatorType = indicator.updateIndicatorType(
+ PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+ taskInfo.windowingMode
+ )
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ moveToFullscreenWithAnimation(taskInfo, position)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ releaseVisualIndicator()
}
- } else if (!indicator.isFullscreen) {
- indicator.transitionFreeformIndicatorToFullscreen()
}
+ // A freeform drag-move ended, remove the indicator immediately.
+ releaseVisualIndicator()
}
/**
@@ -982,22 +991,28 @@ class DesktopTasksController(
* @param y height of drag, to be checked against status bar height.
*/
fun onDragPositioningEndThroughStatusBar(
+ inputCoordinates: PointF,
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- finalizeDragToDesktop(taskInfo, freeformBounds)
- }
-
- private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
- return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
- }
-
- /**
- * Returns the threshold at which we transition a task into freeform when dragging a
- * fullscreen task down from the status bar
- */
- private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
- return 2 * getStatusBarHeight(taskInfo)
+ val indicator = visualIndicator ?: return
+ val indicatorType = indicator
+ .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, freeformBounds)
+ }
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ cancelDragToDesktop(taskInfo)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT))
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT))
+ }
+ }
}
/**
@@ -1037,6 +1052,50 @@ class DesktopTasksController(
desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
+ override fun onUnhandledDrag(
+ launchIntent: PendingIntent,
+ dragSurface: SurfaceControl,
+ onFinishCallback: Consumer<Boolean>
+ ): Boolean {
+ // TODO(b/320797628): Pass through which display we are dropping onto
+ val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
+ if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ // Not currently in desktop mode, ignore the drop
+ return false
+ }
+
+ val launchComponent = getComponent(launchIntent)
+ if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
+ // TODO(b/320797628): Should only return early if there is an existing running task, and
+ // notify the user as well. But for now, just ignore the drop.
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ return false
+ }
+
+ // Start a new transition to launch the app
+ val opts = ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ pendingIntentLaunchFlags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ }
+ val wct = WindowContainerTransaction()
+ wct.sendPendingIntent(launchIntent, null, opts.toBundle())
+ transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+
+ // Report that this is handled by the listener
+ onFinishCallback.accept(true)
+
+ // We've assumed responsibility of cleaning up the drag surface, so do that now
+ // TODO(b/320797628): Do an actual animation here for the drag surface
+ val t = SurfaceControl.Transaction()
+ t.remove(dragSurface)
+ t.apply()
+ return true
+ }
+
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
@@ -1063,6 +1122,18 @@ class DesktopTasksController(
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
+
+ override fun enterDesktop(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterDesktop(displayId)
+ }
+ }
+
+ override fun moveFocusedTaskToFullscreen(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterFullscreen(displayId)
+ }
+ }
}
/** The interface for calls from outside the host process. */
@@ -1074,14 +1145,16 @@ class DesktopTasksController(
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visible
+ visibleTasksCount
)
- remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ remoteListener.call {
+ l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
@@ -1181,13 +1254,9 @@ class DesktopTasksController(
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284)
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
- // Override default freeform task width when desktop mode is enabled. In dips.
- private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
- SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
-
- // Override default freeform task height when desktop mode is enabled. In dips.
- private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
- SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+ @JvmField
+ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
+ .getInt("persist.wm.debug.freeform_initial_bounds_scale", 75) / 100f
/**
* Check if desktop density override is enabled
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5c416f..af26e2980afe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -26,6 +26,7 @@ import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -33,10 +34,9 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.util.TransitionUtil
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/**
@@ -69,6 +69,7 @@ class DragToDesktopTransitionHandler(
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
private var splitScreenController: SplitScreenController? = null
private var transitionState: TransitionState? = null
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
/** Whether a drag-to-desktop transition is in progress. */
val inProgress: Boolean
@@ -84,6 +85,10 @@ class DragToDesktopTransitionHandler(
splitScreenController = controller
}
+ fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
+ }
+
/**
* Starts a transition that performs a transient launch of Home so that Home is brought to the
* front while still keeping the currently focused task that is being dragged resumed. This
@@ -96,10 +101,13 @@ class DragToDesktopTransitionHandler(
fun startDragToDesktopTransition(
taskId: Int,
dragToDesktopAnimator: MoveToDesktopAnimator,
- windowDecoration: DesktopModeWindowDecoration
) {
if (inProgress) {
- error("A drag to desktop is already in progress")
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: Drag to desktop transition already in progress."
+ )
+ return
}
val options = ActivityOptions.makeBasic().apply {
@@ -124,14 +132,12 @@ class DragToDesktopTransitionHandler(
TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
}
@@ -144,6 +150,12 @@ class DragToDesktopTransitionHandler(
* inside the desktop drop zone.
*/
fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+ if (!inProgress) {
+ // Don't attempt to finish a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
if (requireTransitionState().startAborted) {
// Don't attempt to complete the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +173,12 @@ class DragToDesktopTransitionHandler(
* means the user wants to remain in their current windowing mode.
*/
fun cancelDragToDesktopTransition() {
+ if (!inProgress) {
+ // Don't attempt to cancel a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
val state = requireTransitionState()
if (state.startAborted) {
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
@@ -223,7 +241,7 @@ class DragToDesktopTransitionHandler(
show(change.leash)
}
} else if (TransitionInfo.isIndependent(change, info)) {
- // Root.
+ // Root(s).
when (state) {
is TransitionState.FromSplit -> {
state.splitRootChange = change
@@ -240,6 +258,9 @@ class DragToDesktopTransitionHandler(
}
}
is TransitionState.FromFullscreen -> {
+ // Most of the time we expect one change/task here, which should be the
+ // same that initiated the drag and that should be layered on top of
+ // everything.
if (change.taskInfo?.taskId == state.draggedTaskId) {
state.draggedTaskChange = change
val bounds = change.endAbsBounds
@@ -249,7 +270,18 @@ class DragToDesktopTransitionHandler(
show(change.leash)
}
} else {
- throw IllegalStateException("Expected root to be dragged task")
+ // It's possible to see an additional change that isn't the dragged
+ // task when the dragged task is translucent and so the task behind it
+ // is included in the transition since it was visible and is now being
+ // occluded by the Home task. Just layer it at the bottom and save it
+ // in case we need to restore order if the drag is cancelled.
+ state.otherRootChanges.add(change)
+ val bounds = change.endAbsBounds
+ startTransaction.apply {
+ setLayer(change.leash, appLayers - i)
+ setWindowCrop(change.leash, bounds.width(), bounds.height())
+ show(change.leash)
+ }
}
}
}
@@ -375,7 +407,7 @@ class DragToDesktopTransitionHandler(
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
- state.windowDecoration.showResizeVeil(t, animStartBounds)
+ onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t, animStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
// Because the task surface was scaled down during the drag, we must use the animated
@@ -399,11 +431,15 @@ class DragToDesktopTransitionHandler(
animBounds.height()
)
}
- state.windowDecoration.updateResizeVeil(tx, animBounds)
+ onTaskResizeAnimationListener.onBoundsChange(
+ state.draggedTaskId,
+ tx,
+ animBounds
+ )
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- state.windowDecoration.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
}
@@ -499,8 +535,18 @@ class DragToDesktopTransitionHandler(
val wct = WindowContainerTransaction()
when (state) {
is TransitionState.FromFullscreen -> {
+ // There may have been tasks sent behind home that are not the dragged task (like
+ // when the dragged task is translucent and that makes the task behind it visible).
+ // Restore the order of those first.
+ state.otherRootChanges.mapNotNull { it.container }.forEach { wc ->
+ // TODO(b/322852244): investigate why even though these "other" tasks are
+ // reordered in front of home and behind the translucent dragged task, its
+ // surface is not visible on screen.
+ wct.reorder(wc, true /* toTop */)
+ }
val wc = state.draggedTaskChange?.container
?: error("Dragged task should be non-null before cancelling")
+ // Then the dragged task a the very top.
wct.reorder(wc, true /* toTop */)
}
is TransitionState.FromSplit -> {
@@ -536,7 +582,6 @@ class DragToDesktopTransitionHandler(
sealed class TransitionState {
abstract val draggedTaskId: Int
abstract val dragAnimator: MoveToDesktopAnimator
- abstract val windowDecoration: DesktopModeWindowDecoration
abstract val startTransitionToken: IBinder
abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
@@ -549,7 +594,6 @@ class DragToDesktopTransitionHandler(
data class FromFullscreen(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
@@ -558,11 +602,11 @@ class DragToDesktopTransitionHandler(
override var draggedTaskChange: Change? = null,
override var cancelled: Boolean = false,
override var startAborted: Boolean = false,
+ var otherRootChanges: MutableList<Change> = mutableListOf()
) : TransitionState()
data class FromSplit(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 605600f54823..79bb5408df82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -38,7 +38,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
import java.util.ArrayList;
import java.util.List;
@@ -54,13 +54,11 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
private final Transitions mTransitions;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- // The size of the screen after drag relative to the fullscreen size
- public static final float FINAL_FREEFORM_SCALE = 0.6f;
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
+ private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
this(transitions, SurfaceControl.Transaction::new);
@@ -73,14 +71,15 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
mTransactionSupplier = supplier;
}
+ void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) {
+ mOnTaskResizeAnimationListener = listener;
+ }
+
/**
* Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
* @param wct WindowContainerTransaction for transition
- * @param decor {@link DesktopModeWindowDecoration} of task being animated
*/
- public void moveToDesktop(@NonNull WindowContainerTransaction wct,
- DesktopModeWindowDecoration decor) {
- mDesktopModeWindowDecoration = decor;
+ public void moveToDesktop(@NonNull WindowContainerTransaction wct) {
final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this);
mPendingTransitionTokens.add(token);
}
@@ -136,33 +135,33 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
@NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mDesktopModeWindowDecoration == null) {
- Slog.e(TAG, "Window Decoration is not available for this transition");
+ final SurfaceControl leash = change.getLeash();
+ final Rect startBounds = change.getStartAbsBounds();
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (mOnTaskResizeAnimationListener == null) {
+ Slog.e(TAG, "onTaskResizeAnimationListener is not available for this transition");
return false;
}
- final SurfaceControl leash = change.getLeash();
- final Rect startBounds = change.getStartAbsBounds();
- startT.setPosition(leash, startBounds.left, startBounds.right)
+ startT.setPosition(leash, startBounds.left, startBounds.top)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash);
- mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
-
+ mOnTaskResizeAnimationListener.onAnimationStart(taskInfo.taskId, startT, startBounds);
final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
change.getStartAbsBounds(), change.getEndAbsBounds());
animator.setDuration(FREEFORM_ANIMATION_DURATION);
SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
final Rect animationValue = (Rect) animator.getAnimatedValue();
- t.setPosition(leash, animationValue.left, animationValue.right)
+ t.setPosition(leash, animationValue.left, animationValue.top)
.setWindowCrop(leash, animationValue.width(), animationValue.height())
.show(leash);
- mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+ mOnTaskResizeAnimationListener.onBoundsChange(taskInfo.taskId, t, animationValue);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopModeWindowDecoration.hideResizeVeil();
+ mOnTaskResizeAnimationListener.onAnimationEnd(taskInfo.taskId);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 39128a863ec9..8ed87f23bf40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -22,8 +22,8 @@ package com.android.wm.shell.desktopmode;
*/
interface IDesktopTaskListener {
- /** Desktop task visibility has change. Visible if at least 1 task is visible. */
- oneway void onVisibilityChanged(int displayId, boolean visible);
+ /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
+ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
/** Desktop task stashed status has changed. */
oneway void onStashedChanged(int displayId, boolean stashed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9debb25ff296..c469e652b117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,7 +21,6 @@ import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.graphics.Rect
import android.os.IBinder
-import android.util.SparseArray
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
@@ -30,7 +29,7 @@ import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/** Handles the animation of quick resizing of desktop tasks. */
@@ -40,7 +39,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
- private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
private var boundsAnimator: Animator? = null
@@ -49,15 +48,12 @@ class ToggleResizeDesktopTaskTransitionHandler(
) : this(transitions, Supplier { SurfaceControl.Transaction() })
/** Starts a quick resize transition. */
- fun startTransition(
- wct: WindowContainerTransaction,
- taskId: Int,
- windowDecoration: DesktopModeWindowDecoration
- ) {
- // Pause relayout until the transition animation finishes.
- windowDecoration.incrementRelayoutBlock()
+ fun startTransition(wct: WindowContainerTransaction) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
- taskToDecorationMap.put(taskId, windowDecoration)
+ }
+
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
}
override fun startAnimation(
@@ -72,9 +68,6 @@ class ToggleResizeDesktopTaskTransitionHandler(
val taskId = checkNotNull(change.taskInfo).taskId
val startBounds = change.startAbsBounds
val endBounds = change.endAbsBounds
- val windowDecor =
- taskToDecorationMap.removeReturnOld(taskId)
- ?: throw IllegalStateException("Window decoration not found for task $taskId")
val tx = transactionSupplier.get()
boundsAnimator?.cancel()
@@ -92,7 +85,11 @@ class ToggleResizeDesktopTaskTransitionHandler(
)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
- windowDecor.showResizeVeil(startTransaction, startBounds)
+ onTaskResizeAnimationListener.onAnimationStart(
+ taskId,
+ startTransaction,
+ startBounds
+ )
},
onEnd = {
finishTransaction
@@ -103,7 +100,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
)
.setWindowCrop(leash, endBounds.width(), endBounds.height())
.show(leash)
- windowDecor.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
boundsAnimator = null
}
@@ -113,7 +110,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.show(leash)
- windowDecor.updateResizeVeil(tx, rect)
+ onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
}
start()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348d5aca..3fad28ad232f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@ particular order):
Todo
- Per-feature docs
- Feature flagging
-- Best practices \ No newline at end of file
+- Best practices & patterns \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326eadcd5..9aa5f4ffcd78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@ AIDL interfaces and constants. Currently, all AIDL files, and classes under the
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d962adc9..7070dead9957 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@ developers to jump into a few select files and understand how different componen
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@ In some rare cases, there are base components that can change behavior depending
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4e2a07..438aa768165e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@ building to check the log state (is enabled) before printing the print format st
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
+
+## WindowManager/SurfaceFlinger hierarchy dump
+
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
+```shell
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@ If information should be added to the dump, either:
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6aea2ec..b489fe8ea1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@ Currently, the WMShell library is used to drive the windowing experience on hand
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 000000000000..0e20a8ec275c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e640ba7..30ff6691f503 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@ adb shell dumpsys activity service SystemUIService WMShell
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-``` \ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333facc4..98af930c4486 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@ Flicker tests are tests that perform actions and make assertions on the state in
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac748894432..9d015357b60b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@ the product.
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@ To get the SysUI main thread, you can use the `@Main` annotation.
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index fdfb6f3680b2..7da1b23dd5b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,7 +35,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -51,12 +53,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
@@ -71,14 +73,18 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+ GlobalDragListener.GlobalDragListenerCallback,
DisplayController.OnDisplaysChangedListener,
View.OnDragListener, ComponentCallbacks2 {
@@ -90,6 +96,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
+ private final GlobalDragListener mGlobalDragListener;
+ private final Transitions mTransitions;
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -97,39 +105,40 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
// Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
+ // The current display if a drag is in progress
+ private int mActiveDragDisplay = -1;
+
/**
- * Listener called during drag events, currently just onDragStarted.
+ * Listener called during drag events.
*/
public interface DragAndDropListener {
/** Called when a drag has started. */
- void onDragStarted();
- }
+ default void onDragStarted() {}
- /**
- * Creates {@link DragAndDropController}. Returns {@code null} if the feature is disabled.
- */
- public static DragAndDropController create(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- ShellExecutor mainExecutor) {
- if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
- return null;
+ /** Called when a drag has ended. */
+ default void onDragEnded() {}
+
+ /**
+ * Called when an unhandled drag has occurred. The impl must return true if it decides to
+ * handled the unhandled drag, and it must also call `onFinishCallback` to complete the
+ * drag.
+ */
+ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
+ @NonNull SurfaceControl dragSurface,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ return false;
}
- return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, mainExecutor);
}
- DragAndDropController(Context context,
+ public DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -137,6 +146,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
+ mGlobalDragListener = globalDragListener;
+ mTransitions = transitions;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -154,6 +165,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mGlobalDragListener.setListener(this);
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -187,10 +199,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mListeners.remove(listener);
}
- private void notifyDragStarted() {
+ /**
+ * Notifies all listeners and returns whether any listener handled the callback.
+ */
+ private boolean notifyListeners(Function<DragAndDropListener, Boolean> callback) {
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDragStarted();
+ boolean handled = callback.apply(mListeners.get(i));
+ if (handled) {
+ // Return once the callback reports it has handled it
+ return true;
+ }
}
+ return false;
}
@Override
@@ -276,6 +296,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
if (event.getAction() == ACTION_DRAG_STARTED) {
+ mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
@@ -294,14 +315,17 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
return false;
}
// TODO(b/290391688): Also update the session data with task stack changes
- InstanceId loggerSessionId = mLogger.logStart(event);
- pd.activeDragCount++;
- pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+ pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData());
pd.dragSession.update();
- pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
+ pd.activeDragCount++;
+ pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
setDropTargetWindowVisibility(pd, View.VISIBLE);
- notifyDragStarted();
+ notifyListeners(l -> {
+ l.onDragStarted();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
@@ -335,11 +359,43 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
});
}
mLogger.logEnd();
+ mActiveDragDisplay = -1;
+ notifyListeners(l -> {
+ l.onDragEnded();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
}
return true;
}
+ @Override
+ public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ // Bring the task forward when an item is dropped on it
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(taskInfo.token, true /* onTop */);
+ mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
+ }
+
+ @Override
+ public void onUnhandledDrop(@NonNull DragEvent dragEvent,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ final PendingIntent launchIntent = DragUtils.getLaunchIntent(dragEvent);
+ if (launchIntent == null) {
+ // No intent to launch, report that this is unhandled by the listener
+ onFinishCallback.accept(false);
+ return;
+ }
+
+ final boolean handled = notifyListeners(
+ l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
+ if (!handled) {
+ // Nobody handled this, we still have to notify WM
+ onFinishCallback.accept(false);
+ }
+ }
+
/**
* Handles dropping on the drop target.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
index 2a7dd5aeb341..75b126c47690 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
@@ -53,17 +53,21 @@ public class DragAndDropEventLogger {
/**
* Logs the start of a drag.
*/
- public InstanceId logStart(DragEvent event) {
- final ClipDescription description = event.getClipDescription();
- final ClipData data = event.getClipData();
- final ClipData.Item item = data.getItemAt(0);
- mInstanceId = item.getIntent().getParcelableExtra(
- ClipDescription.EXTRA_LOGGING_INSTANCE_ID);
+ public InstanceId logStart(DragSession session) {
+ mInstanceId = session.appData != null
+ ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID,
+ InstanceId.class)
+ : null;
if (mInstanceId == null) {
mInstanceId = mIdSequence.newInstanceId();
}
- mActivityInfo = item.getActivityInfo();
- log(getStartEnum(description), mActivityInfo);
+ mActivityInfo = session.activityInfo;
+ if (session.appData != null) {
+ log(getStartEnum(session.getClipDescription()), mActivityInfo);
+ } else {
+ // TODO(b/255649902): Update this once we have a new enum
+ log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo);
+ }
return mInstanceId;
}
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 a31a773a76a0..eb82da8a8e9f 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
@@ -29,6 +29,8 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_SHORTCUT_ID;
import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.Intent.EXTRA_USER;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
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;
@@ -52,9 +54,11 @@ import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import androidx.annotation.IntDef;
@@ -63,8 +67,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -104,7 +110,9 @@ public class DragAndDropPolicy {
void start(DragSession session, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
mSession = session;
- RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+ RectF disallowHitRegion = mSession.appData != null
+ ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
+ : null;
if (disallowHitRegion == null) {
mDisallowHitRegion.setEmpty();
} else {
@@ -223,7 +231,7 @@ public class DragAndDropPolicy {
}
@VisibleForTesting
- void handleDrop(Target target, ClipData data) {
+ void handleDrop(Target target) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -238,41 +246,77 @@ public class DragAndDropPolicy {
mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
- final ClipDescription description = data.getDescription();
- final Intent dragData = mSession.dragData;
- startClipDescription(description, dragData, position);
+ if (mSession.appData != null) {
+ launchApp(mSession, position);
+ } else {
+ launchIntent(mSession, position);
+ }
}
- private void startClipDescription(ClipDescription description, Intent intent,
- @SplitPosition int position) {
+ /**
+ * Launches an app provided by SysUI.
+ */
+ private void launchApp(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
+ position);
+ final ClipDescription description = session.getClipDescription();
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
final Bundle opts = baseActivityOpts.toBundle();
- if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
- opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+ if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+ opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
}
// Put BAL flags to avoid activity start aborted.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
- final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+ final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
if (isTask) {
- final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
mStarter.startTask(taskId, position, opts);
} else if (isShortcut) {
- final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
- final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+ final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
+ final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
- final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ final PendingIntent launchIntent =
+ session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
+ if (Build.IS_DEBUGGABLE) {
+ if (!user.equals(launchIntent.getCreatorUserHandle())) {
+ Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
+ }
+ }
mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
}
/**
+ * Launches an intent sender provided by an application.
+ */
+ private void launchIntent(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
+ position);
+ final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+ baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
+ // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
+ baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
+ | FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ final Bundle opts = baseActivityOpts.toBundle();
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+
+ mStarter.startIntent(session.launchableIntent,
+ session.launchableIntent.getCreatorUserHandle().getIdentifier(),
+ null /* fillIntent */, position, opts);
+ }
+
+ /**
* Interface for actually committing the task launches.
*/
public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 445ba897c173..ecb53dc17a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -16,12 +16,14 @@
package com.android.wm.shell.draganddrop;
+import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
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;
@@ -38,12 +40,15 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
@@ -65,7 +70,8 @@ import java.util.ArrayList;
/**
* Coordinates the visible drop targets for the current drag within a single display.
*/
-public class DragLayout extends LinearLayout {
+public class DragLayout extends LinearLayout
+ implements ViewTreeObserver.OnComputeInternalInsetsListener {
// While dragging the status bar is hidden.
private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -90,7 +96,9 @@ public class DragLayout extends LinearLayout {
private int mDisplayMargin;
private int mDividerSize;
+ private int mLaunchIntentEdgeMargin;
private Insets mInsets = Insets.NONE;
+ private Region mTouchableRegion;
private boolean mIsShowing;
private boolean mHasDropped;
@@ -106,10 +114,11 @@ public class DragLayout extends LinearLayout {
mStatusBarManager = context.getSystemService(StatusBarManager.class);
mLastConfiguration.setTo(context.getResources().getConfiguration());
- mDisplayMargin = context.getResources().getDimensionPixelSize(
- R.dimen.drop_layout_display_margin);
- mDividerSize = context.getResources().getDimensionPixelSize(
- R.dimen.split_divider_bar_width);
+ final Resources res = context.getResources();
+ mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin);
+ mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mLaunchIntentEdgeMargin =
+ res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin);
// Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when
// showing the highlight.
@@ -131,6 +140,66 @@ public class DragLayout extends LinearLayout {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTouchableRegion = Region.obtain();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mTouchableRegion.recycle();
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) {
+ if (mSession != null && mSession.launchableIntent != null) {
+ inOutInfo.touchableRegion.set(mTouchableRegion);
+ inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTouchableRegion();
+ }
+
+ /**
+ * Updates the touchable region, this should be called after any configuration changes have
+ * been applied.
+ */
+ private void updateTouchableRegion() {
+ mTouchableRegion.setEmpty();
+ if (mSession != null && mSession.launchableIntent != null) {
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+ if (mIsLeftRightSplit) {
+ mTouchableRegion.union(
+ new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height));
+ mTouchableRegion.union(
+ new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width,
+ height));
+ } else {
+ mTouchableRegion.union(
+ new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin));
+ mTouchableRegion.union(
+ new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width,
+ height));
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Updating drag layout width=%d height=%d touchable region=%s",
+ width, height, mTouchableRegion);
+
+ // Reapply insets to update the touchable region
+ requestApplyInsets();
+ }
+ }
+
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
@@ -164,6 +233,7 @@ public class DragLayout extends LinearLayout {
mDropZoneView2.onThemeChange();
}
mLastConfiguration.setTo(newConfig);
+ requestLayout();
}
private void updateContainerMarginsForSingleTask() {
@@ -242,6 +312,7 @@ public class DragLayout extends LinearLayout {
mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
+ requestLayout();
}
private void updateDropZoneSizesForSingleTask() {
@@ -392,7 +463,7 @@ public class DragLayout extends LinearLayout {
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget, event.getClipData());
+ mPolicy.handleDrop(mCurrentTarget);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -445,18 +516,20 @@ public class DragLayout extends LinearLayout {
}
private void animateFullscreenContainer(boolean visible) {
- mStatusBarManager.disable(visible
- ? HIDE_STATUS_BAR_FLAGS
- : DISABLE_NONE);
+ int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
+ StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
+ DISABLE2_NONE);
+ mStatusBarManager.requestDisabledComponent(disableInfo, "animateFullscreenContainer");
// We're only using the first drop zone if there is one fullscreen target
mDropZoneView1.setShowingMargin(visible);
mDropZoneView1.setShowingHighlight(visible);
}
private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
- mStatusBarManager.disable(visible
- ? HIDE_STATUS_BAR_FLAGS
- : DISABLE_NONE);
+ int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
+ StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
+ DISABLE2_NONE);
+ mStatusBarManager.requestDisabledComponent(disableInfo, "animateSplitContainers");
mDropZoneView1.setShowingMargin(visible);
mDropZoneView2.setShowingMargin(visible);
Animator animator = mDropZoneView1.getAnimator();
@@ -503,5 +576,7 @@ public class DragLayout extends LinearLayout {
pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ pw.println(innerPrefix + "mInsets=" + mInsets);
+ pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 353d702e5bc4..8f1bc59af1ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -21,12 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.Context;
+import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.common.DisplayLayout;
import java.util.List;
@@ -39,7 +42,18 @@ public class DragSession {
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
- Intent dragData;
+ // The activity info associated with the activity in the appData or the launchableIntent
+ @Nullable
+ ActivityInfo activityInfo;
+ // The intent bundle that includes data about an app-type drag that is started by
+ // Launcher/SysUI. Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ Intent appData;
+ // A launchable intent that is specified in the ClipData directly.
+ // Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ PendingIntent launchableIntent;
+ // Stores the current running task at the time that the drag was initiated
ActivityManager.RunningTaskInfo runningTaskInfo;
@WindowConfiguration.WindowingMode
int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@@ -47,7 +61,7 @@ public class DragSession {
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
- DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
@@ -55,6 +69,14 @@ public class DragSession {
}
/**
+ * Returns the clip description associated with the drag.
+ * @return
+ */
+ ClipDescription getClipDescription() {
+ return mInitialDragData.getDescription();
+ }
+
+ /**
* Updates the session data based on the current state of the system.
*/
void update() {
@@ -67,9 +89,11 @@ public class DragSession {
runningTaskActType = task.getActivityType();
}
- final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
- dragItemSupportsSplitscreen = info == null
- || ActivityInfo.isResizeableMode(info.resizeMode);
- dragData = mInitialDragData.getItemAt(0).getIntent();
+ activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
+ // TODO: This should technically check & respect config_supportsNonResizableMultiWindow
+ dragItemSupportsSplitscreen = activityInfo == null
+ || ActivityInfo.isResizeableMode(activityInfo.resizeMode);
+ appData = mInitialDragData.getItemAt(0).getIntent();
+ launchableIntent = DragUtils.getLaunchIntent(mInitialDragData);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 7c0883d2538f..24f8e186bf76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -20,9 +20,14 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import android.app.PendingIntent;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -31,8 +36,21 @@ public class DragUtils {
* Returns whether we can handle this particular drag.
*/
public static boolean canHandleDrag(DragEvent event) {
- return event.getClipData().getItemCount() > 0
- && (isAppDrag(event.getClipDescription()));
+ if (event.getClipData().getItemCount() <= 0) {
+ // No clip data, ignore this drag
+ return false;
+ }
+ if (isAppDrag(event.getClipDescription())) {
+ // Clip data contains an app drag initiated from SysUI, handle it
+ return true;
+ }
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()
+ && getLaunchIntent(event) != null) {
+ // Clip data contains a launchable intent drag, handle it
+ return true;
+ }
+ // Otherwise ignore
+ return false;
}
/**
@@ -45,6 +63,31 @@ public class DragUtils {
}
/**
+ * Returns a launchable intent in the given `DragEvent` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) {
+ return getLaunchIntent(dragEvent.getClipData());
+ }
+
+ /**
+ * Returns a launchable intent in the given `ClipData` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull ClipData data) {
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent intent = new PendingIntent(item.getIntentSender().getTarget());
+ if (intent != null && intent.isActivity()) {
+ return intent;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns a list of the mime types provided in the clip description.
*/
public static String getMimeTypesConcatenated(ClipDescription description) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
new file mode 100644
index 000000000000..8826141fb406
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.draganddrop
+
+import android.app.ActivityManager
+import android.os.RemoteException
+import android.util.Log
+import android.view.DragEvent
+import android.view.IWindowManager
+import android.window.IGlobalDragListener
+import android.window.IUnhandledDragCallback
+import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import java.util.function.Consumer
+
+/**
+ * Manages the listener and callbacks for unhandled global drags.
+ * This is only used by DragAndDropController and should not be used directly by other classes.
+ */
+class GlobalDragListener(
+ private val wmService: IWindowManager,
+ private val mainExecutor: ShellExecutor
+) {
+ private var callback: GlobalDragListenerCallback? = null
+
+ private val globalDragListener: IGlobalDragListener =
+ object : IGlobalDragListener.Stub() {
+ override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ mainExecutor.execute() {
+ this@GlobalDragListener.onCrossWindowDrop(taskInfo)
+ }
+ }
+
+ override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
+ mainExecutor.execute() {
+ this@GlobalDragListener.onUnhandledDrop(event, callback)
+ }
+ }
+ }
+
+ /**
+ * Callbacks for global drag events.
+ */
+ interface GlobalDragListenerCallback {
+ /**
+ * Called when a global drag is successfully handled by another window.
+ */
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
+
+ /**
+ * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
+ * dropped on a window that does not want to handle it).
+ *
+ * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
+ * also responsible for releasing up the drag surface provided via the drag event.
+ */
+ fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
+ }
+
+ /**
+ * Sets a listener for callbacks when an unhandled drag happens.
+ */
+ fun setListener(listener: GlobalDragListenerCallback?) {
+ val updateWm = (callback == null && listener != null)
+ || (callback != null && listener == null)
+ callback = listener
+ if (updateWm) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "%s unhandled drag listener",
+ if (callback != null) "Registering" else "Unregistering")
+ wmService.setGlobalDragListener(
+ if (callback != null) globalDragListener else null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to set unhandled drag listener")
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onCrossWindowDrop: %s", taskInfo)
+ callback?.onCrossWindowDrop(taskInfo)
+ }
+
+ @VisibleForTesting
+ fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onUnhandledDrop: %s", dragEvent)
+ if (callback == null) {
+ wmCallback.notifyUnhandledDropComplete(false)
+ return
+ }
+
+ callback?.onUnhandledDrop(dragEvent) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Notifying onUnhandledDrop complete: %b", it)
+ wmCallback.notifyUnhandledDropComplete(it)
+ }
+ }
+
+ companion object {
+ private val TAG = GlobalDragListener::class.java.simpleName
+ }
+}
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 a80241e0ac5c..f2bdcae31956 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.freeform;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
@@ -151,6 +153,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
@Override
public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6b6a7bc42046..ffcc526eac40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,7 +112,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
onChangeTransitionReady(change, startT, finishT);
break;
}
- mWindowDecorViewModel.onTransitionReady(transition, info, change);
}
mTransitionToTaskInfo.put(transition, taskInfoList);
}
@@ -153,8 +152,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- mWindowDecorViewModel.onTransitionMerged(merged, playing);
-
final List<ActivityManager.RunningTaskInfo> infoOfMerged =
mTransitionToTaskInfo.get(merged);
if (infoOfMerged == null) {
@@ -178,7 +175,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
final List<ActivityManager.RunningTaskInfo> taskInfo =
mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
mTransitionToTaskInfo.remove(transition);
- mWindowDecorViewModel.onTransitionFinished(transition);
for (int i = 0; i < taskInfo.size(); ++i) {
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index e63bbc07bc41..73de231fb63a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -27,7 +27,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
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 3635165d76ce..87e372cc304c 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
@@ -25,7 +25,6 @@ import static android.util.RotationUtils.rotateBounds;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
-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.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -64,6 +63,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.util.Rational;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -86,6 +86,8 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -111,7 +113,7 @@ import java.util.function.IntConsumer;
* see also {@link PipMotionHelper}.
*/
public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
- DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
+ DisplayController.OnDisplaysChangedListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
/**
@@ -125,6 +127,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
SystemProperties.getInt(
"persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
+ private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f;
+
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
@@ -140,6 +144,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final int mCrossFadeAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -157,10 +162,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
private boolean mIsCancelled;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(
+ PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
@Override
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout,
+ "PipTaskOrganizer::mPipAnimationCallback");
+ }
+
final int direction = animator.getTransitionDirection();
mIsCancelled = false;
sendOnPipTransitionStarted(direction);
@@ -169,6 +194,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
+ // Close the high perf session if needed.
+ cleanUpHighPerfSessionMaybe();
+
final int direction = animator.getTransitionDirection();
if (mIsCancelled) {
sendOnPipTransitionFinished(direction);
@@ -334,6 +362,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Nullable
SurfaceControl mPipOverlay;
+ /**
+ * The app bounds used for the buffer size of the
+ * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ *
+ * Note that this is empty if the overlay is removed or if it's some other type of overlay
+ * defined in {@link PipContentOverlay}.
+ */
+ @NonNull
+ final Rect mAppBounds = new Rect();
+
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@@ -346,6 +384,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipTransitionController pipTransitionController,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -371,6 +410,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -379,7 +419,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
- mTaskOrganizer.addFocusListener(this);
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
@@ -464,15 +503,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
+ "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
// do nothing if there is no startSwipePipToHome being called before
if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
return;
}
mPipBoundsState.setBounds(destinationBounds);
- mPipOverlay = overlay;
+ setContentOverlay(overlay, appBounds);
if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
// With Shell transition, the overlay was attached to the remote transition leash, which
// will be removed when the current transition is finished, so we need to reparent it
@@ -589,9 +628,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
- isPipToTopLeft()
- ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mSplitScreenOptional.get().onPipExpandToSplit(wct, mTaskInfo);
mPipTransitionController.startExitTransition(
TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
return;
@@ -733,6 +770,37 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPictureInPictureParams.getTitle());
mPipParamsChangedForwarder.notifySubtitleChanged(
mPictureInPictureParams.getSubtitle());
+
+ if (mPictureInPictureParams.hasSourceBoundsHint()
+ && mPictureInPictureParams.hasSetAspectRatio()) {
+ Rational sourceRectHintAspectRatio = new Rational(
+ mPictureInPictureParams.getSourceRectHint().width(),
+ mPictureInPictureParams.getSourceRectHint().height());
+ if (sourceRectHintAspectRatio.compareTo(
+ mPictureInPictureParams.getAspectRatio()) != 0) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Aspect ratio of source rect hint (%d/%d) does not match the provided "
+ + "aspect ratio value (%d/%d). Consider matching them for "
+ + "improved animation. Future releases might override the "
+ + "value to match.",
+ mPictureInPictureParams.getSourceRectHint().width(),
+ mPictureInPictureParams.getSourceRectHint().height(),
+ mPictureInPictureParams.getAspectRatio().getNumerator(),
+ mPictureInPictureParams.getAspectRatio().getDenominator());
+ }
+ if (Math.abs(sourceRectHintAspectRatio.floatValue()
+ - mPictureInPictureParams.getAspectRatioFloat())
+ > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Aspect ratio of source rect hint (%f) does not match the provided "
+ + "aspect ratio value (%f) and is above threshold of %f. "
+ + "Consider matching them for improved animation. Future "
+ + "releases might override the value to match.",
+ sourceRectHintAspectRatio.floatValue(),
+ mPictureInPictureParams.getAspectRatioFloat(),
+ PIP_ASPECT_RATIO_MISMATCH_THRESHOLD);
+ }
+ }
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
@@ -1017,11 +1085,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
@Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mPipMenuController.onFocusTaskChanged(taskInfo);
- }
-
- @Override
public boolean supportCompatUI() {
// PIP doesn't support compat.
return false;
@@ -1882,13 +1945,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removeContentOverlay: %s, state=%s, surface=%s",
+ mTaskInfo, mPipTransitionState, surface);
if (mPipOverlay != null) {
if (mPipOverlay != surface) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: trying to remove overlay (%s) which is not local reference (%s)",
TAG, surface, mPipOverlay);
}
- mPipOverlay = null;
+ clearContentOverlay();
}
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
@@ -1905,6 +1971,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (callback != null) callback.run();
}
+ void clearContentOverlay() {
+ mPipOverlay = null;
+ mAppBounds.setEmpty();
+ }
+
+ void setContentOverlay(@Nullable SurfaceControl leash, @NonNull Rect appBounds) {
+ mPipOverlay = leash;
+ if (mPipOverlay != null) {
+ mAppBounds.set(appBounds);
+ } else {
+ mAppBounds.setEmpty();
+ }
+ }
+
private void resetShadowRadius() {
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// mLeash is undefined when in PipTransitionState.UNDEFINED
@@ -1950,9 +2030,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
pw.println(innerPrefix + "mToken=" + mToken
+ " binder=" + (mToken != null ? mToken.asBinder() : null));
pw.println(innerPrefix + "mLeash=" + mLeash);
+ pw.println(innerPrefix + "mPipOverlay=" + mPipOverlay);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
mPipTransitionController.dump(pw, innerPrefix);
+ if (mPipPerfHintController != null) {
+ mPipPerfHintController.dump(pw, innerPrefix);
+ }
}
@Override
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 f5f15d81ea44..6a1a62ea30a1 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
@@ -68,13 +68,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -141,8 +142,6 @@ public class PipTransition extends PipTransitionController {
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
- private Rect mInitBounds = new Rect();
-
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -287,12 +286,6 @@ public class PipTransition extends PipTransitionController {
// 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);
}
@@ -465,12 +458,13 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
- if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+ final Rect appBounds = mPipOrganizer.mAppBounds;
+ if (mPipOrganizer.mPipOverlay != null && !appBounds.isEmpty()) {
// Resetting the scale for pinned task while re-adjusting its crop,
// also scales the overlay. So we need to update the overlay leash too.
Rect overlayBounds = new Rect(destinationBounds);
final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mInitBounds, destinationBounds);
+ .getOverlaySize(appBounds, destinationBounds);
overlayBounds.offsetTo(
(destinationBounds.width() - overlaySize) / 2,
@@ -479,7 +473,6 @@ public class PipTransition extends PipTransitionController {
mPipOrganizer.mPipOverlay, overlayBounds);
}
}
- mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -617,7 +610,7 @@ public class PipTransition extends PipTransitionController {
// if overlay is present remove it immediately, as exit transition came before it faded out
if (mPipOrganizer.mPipOverlay != null) {
startTransaction.remove(mPipOrganizer.mPipOverlay);
- clearPipOverlay();
+ mPipOrganizer.clearContentOverlay();
}
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -951,9 +944,6 @@ public class PipTransition extends PipTransitionController {
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
- // Cache the start bounds for overlay manipulations as a part of finishCallback.
- mInitBounds.set(currentBounds);
-
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -992,10 +982,12 @@ public class PipTransition extends PipTransitionController {
0 /* startingAngle */, rotationDelta);
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
- // animation.
+ // animation. We also temporarily disallow app icon overlay and use color overlay
+ // instead when in fixed rotation enter PiP in button nav with no sourceRectHint.
+ // TODO(b/319286295): Fix App Icon Overlay animation in fixed rotation in btn nav.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
- if (hasTopActivityInfo) {
+ if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
animator.setAppIconContentOverlay(
mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
@@ -1022,7 +1014,7 @@ public class PipTransition extends PipTransitionController {
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
- mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
+ mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -1045,6 +1037,7 @@ public class PipTransition extends PipTransitionController {
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
mPipDisplayLayoutState.rotateTo(endRotation);
+ mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
@@ -1073,10 +1066,6 @@ public class PipTransition extends PipTransitionController {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
- Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
- if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
- mInitBounds.set(appBounds);
- }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -1106,7 +1095,7 @@ public class PipTransition extends PipTransitionController {
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- this::clearPipOverlay /* callback */, false /* withStartDelay */);
+ null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1250,10 +1239,6 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.updateMenuBounds(destinationBounds);
}
- private void clearPipOverlay() {
- mPipOrganizer.mPipOverlay = null;
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
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 04911c0bc064..32442f740a52 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
@@ -23,12 +23,16 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.Flags;
import android.app.PictureInPictureParams;
+import android.app.PictureInPictureUiState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -37,16 +41,20 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -116,6 +124,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+ * client completes any potential draws upon WM state updates.
+ */
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the transition animation can't continue (eg. task is removed during
* animation)
*/
@@ -168,6 +187,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionStarted(direction, pipBounds);
}
+ if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
+ try {
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
+ new PictureInPictureUiState.Builder()
+ .setTransitioningToPip(true)
+ .build());
+ } catch (RemoteException | IllegalStateException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Failed to set alert PiP state change.");
+ }
+ }
}
protected void sendOnPipTransitionFinished(
@@ -176,6 +206,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionFinished(direction);
}
+ if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
+ try {
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
+ new PictureInPictureUiState.Builder()
+ .setTransitioningToPip(false)
+ .build());
+ } catch (RemoteException | IllegalStateException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Failed to set alert PiP state change.");
+ }
+ }
}
protected void sendOnPipTransitionCancelled(
@@ -264,6 +305,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH
public void end() {
}
+ /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
+ public void startHighPerfSession() {}
+
+ /** Closes the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
+ public void closeHighPerfSession() {}
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 760652625f9e..0169e8c40f45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -19,12 +19,9 @@ package com.android.wm.shell.pip.phone;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.Context;
-import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
@@ -41,16 +38,13 @@ import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
/**
* Manages the PiP menu view which can show menu options or a scrim.
@@ -99,30 +93,18 @@ public class PhonePipMenuController implements PipMenuController {
* Called when the PIP requested to show the menu.
*/
void onPipShowMenu();
-
- /**
- * Called when the PIP requested to enter Split.
- */
- void onEnterSplit();
}
- private final Matrix mMoveTransform = new Matrix();
- private final Rect mTmpSourceBounds = new Rect();
- private final RectF mTmpSourceRectF = new RectF();
- private final RectF mTmpDestinationRectF = new RectF();
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final PipMediaController mMediaController;
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
- mSurfaceControlTransactionFactory;
private final float[] mTmpTransform = new float[9];
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
- private final Optional<SplitScreenController> mSplitScreenController;
private final PipUiEventLogger mPipUiEventLogger;
private List<RemoteAction> mAppActions;
@@ -145,7 +127,6 @@ public class PhonePipMenuController implements PipMenuController {
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
- Optional<SplitScreenController> splitScreenOptional,
PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
mContext = context;
@@ -154,11 +135,7 @@ public class PhonePipMenuController implements PipMenuController {
mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
- mSplitScreenController = splitScreenOptional;
mPipUiEventLogger = pipUiEventLogger;
-
- mSurfaceControlTransactionFactory =
- new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
}
public boolean isMenuVisible() {
@@ -190,7 +167,7 @@ public class PhonePipMenuController implements PipMenuController {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
- mSplitScreenController, mPipUiEventLogger);
+ mPipUiEventLogger);
mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -251,13 +228,6 @@ public class PhonePipMenuController implements PipMenuController {
updateMenuLayout(destinationBounds);
}
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (mPipMenuView != null) {
- mPipMenuView.onFocusTaskChanged(taskInfo);
- }
- }
-
/**
* Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some
* reason (ie. the window isn't ready yet, thus {@link android.view.ViewRootImpl} is
@@ -485,10 +455,6 @@ public class PhonePipMenuController implements PipMenuController {
mListeners.forEach(Listener::onPipDismiss);
}
- void onEnterSplit() {
- mListeners.forEach(Listener::onEnterSplit);
- }
-
/**
* @return the best set of actions to show in the PiP menu.
*/
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 63f20fd8e997..4d47ca998d8b 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
@@ -48,7 +48,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Pair;
-import android.util.Size;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -75,6 +74,8 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -85,8 +86,6 @@ import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-import com.android.wm.shell.pip.IPip;
-import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -123,6 +122,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
+ private static final long ENABLE_TOUCH_DELAY_MS = 200L;
+
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -153,6 +154,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
this::onKeepClearAreasChangedCallback;
+ private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true);
+
private void onKeepClearAreasChangedCallback() {
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
@@ -244,6 +247,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// The same rotation may have been set by auto PiP-able or fixed rotation. So notify
// the change with fromRotation=false to apply the rotated destination bounds from
// PipTaskOrganizer#onMovementBoundsChanged.
+ // We need to update the bounds scale in case this was from fixed rotation, as the
+ // current proportion was computed using the previous orientation max size and is wrong.
+ mPipBoundsState.updateBoundsScale();
updateMovementBounds(null, false /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
return;
@@ -974,16 +980,25 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
+ // cache current min/max size
+ Point minSize = mPipBoundsState.getMinSize();
+ Point maxSize = mPipBoundsState.getMaxSize();
+ mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
+ // restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
+ // to reflect the pre-rotation state for it to work
+ mPipBoundsState.setMinSize(minSize.x, minSize.y);
+ mPipBoundsState.setMaxSize(maxSize.x, maxSize.y);
// sync mPipBoundsState with the newly calculated bounds.
mPipBoundsState.setNormalBounds(entryBounds);
return entryBounds;
}
private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
- mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+ SurfaceControl overlay, Rect appBounds) {
+ mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+ appBounds);
}
private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1026,6 +1041,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
saveReentryState(pipBounds);
}
// Disable touches while the animation is running
+ mMainExecutor.removeCallbacks(mEnableTouchCallback);
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
@@ -1038,22 +1054,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/** Save the state to restore to on re-entry. */
public void saveReentryState(Rect pipBounds) {
float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-
- if (!mPipBoundsState.hasUserResizedPip()) {
- mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
- return;
- }
-
- Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
-
- // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
- // fallback to using the userResizeBounds if userResizeBounds are not empty
- if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
- Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
- reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
- }
-
- mPipBoundsState.saveReentryState(reentrySize, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
}
@Override
@@ -1071,7 +1072,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
// Re-enable touches after the animation completes
- mTouchHandler.setTouchEnabled(true);
+ mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS);
mTouchHandler.onPinnedStackAnimationEnded(direction);
}
@@ -1143,6 +1144,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Update the display layout
mPipDisplayLayoutState.rotateTo(toRotation);
+ mTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+
+ postChangeStackBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1280,13 +1286,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay) {
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
if (overlay != null) {
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> controller.stopSwipePipToHome(
- taskId, componentName, destinationBounds, overlay));
+ taskId, componentName, destinationBounds, overlay, appBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 4e75847b6bc0..f92938989637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -131,7 +131,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
});
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
// Show the dismiss target, in case the initial touch event occurred within
// the magnetic field radius.
if (mEnableDismissDragToEdge) {
@@ -141,6 +142,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
if (wasFlungOut) {
mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
@@ -151,7 +153,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
if (mEnableDismissDragToEdge) {
mMainExecutor.executeDelayed(() -> {
mMotionHelper.notifyDismissalPending();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
index 06446573840c..321b739920f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
@@ -32,7 +32,6 @@ public class PipMenuIconsAlgorithm {
protected ViewGroup mViewRoot;
protected ViewGroup mTopEndContainer;
protected View mDragHandle;
- protected View mEnterSplitButton;
protected View mSettingsButton;
protected View mDismissButton;
@@ -43,11 +42,10 @@ public class PipMenuIconsAlgorithm {
* Bind the necessary views.
*/
public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
- View enterSplitButton, View settingsButton, View dismissButton) {
+ View settingsButton, View dismissButton) {
mViewRoot = viewRoot;
mTopEndContainer = topEndContainer;
mDragHandle = dragHandle;
- mEnterSplitButton = enterSplitButton;
mSettingsButton = settingsButton;
mDismissButton = dismissButton;
}
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 63cef9e41f98..15342be0e8b7 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.pip.phone;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
@@ -35,10 +34,8 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
-import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -69,14 +66,12 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
/**
* Translucent window that gets started on top of a task in PIP to allow the user to control it.
@@ -114,7 +109,6 @@ public class PipMenuView extends FrameLayout {
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
private int mDismissFadeOutDurationMs;
- private boolean mFocusedTaskAllowSplitScreen;
private final List<RemoteAction> mActions = new ArrayList<>();
private RemoteAction mCloseAction;
@@ -127,7 +121,6 @@ public class PipMenuView extends FrameLayout {
private AnimatorSet mMenuContainerAnimator;
private final PhonePipMenuController mController;
- private final Optional<SplitScreenController> mSplitScreenControllerOptional;
private final PipUiEventLogger mPipUiEventLogger;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
@@ -152,7 +145,6 @@ public class PipMenuView extends FrameLayout {
protected View mViewRoot;
protected View mSettingsButton;
protected View mDismissButton;
- protected View mEnterSplitButton;
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
@@ -161,14 +153,12 @@ public class PipMenuView extends FrameLayout {
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler,
- Optional<SplitScreenController> splitScreenController,
PipUiEventLogger pipUiEventLogger) {
super(context, null, 0);
mContext = context;
mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
- mSplitScreenControllerOptional = splitScreenController;
mPipUiEventLogger = pipUiEventLogger;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -200,17 +190,6 @@ public class PipMenuView extends FrameLayout {
}
});
- mEnterSplitButton = findViewById(R.id.enter_split);
- mEnterSplitButton.setAlpha(0);
- mEnterSplitButton.setOnClickListener(v -> {
- if (mEnterSplitButton.getAlpha() != 0) {
- enterSplit();
- }
- });
-
- // this disables the ripples
- mEnterSplitButton.setEnabled(false);
-
findViewById(R.id.resize_handle).setAlpha(0);
mActionsGroup = findViewById(R.id.actions_group);
@@ -218,8 +197,7 @@ public class PipMenuView extends FrameLayout {
R.dimen.pip_between_action_padding_land);
mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
- findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton,
- mDismissButton);
+ findViewById(R.id.resize_handle), mSettingsButton, mDismissButton);
mDismissFadeOutDurationMs = context.getResources()
.getInteger(R.integer.config_pipExitAnimationDuration);
@@ -281,22 +259,10 @@ public class PipMenuView extends FrameLayout {
return super.dispatchGenericMotionEvent(event);
}
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
- && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
- taskInfo.taskId);
- mFocusedTaskAllowSplitScreen = isSplitScreen
- || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && taskInfo.supportsMultiWindow
- && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME);
- }
-
void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
mAllowMenuTimeout = allowMenuTimeout;
mDidLastShowMenuResize = resizeMenuOnShow;
- final boolean enableEnterSplit =
- mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton);
if (mMenuState != menuState) {
// Disallow touches if the menu needs to resize while showing, and we are transitioning
// to/from a full menu state.
@@ -315,14 +281,8 @@ public class PipMenuView extends FrameLayout {
mSettingsButton.getAlpha(), 1f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 1f);
- ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
- mEnterSplitButton.getAlpha(),
- enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f);
if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
- enterSplitAnim);
- } else {
- mMenuContainerAnimator.playTogether(enterSplitAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
@@ -375,7 +335,6 @@ public class PipMenuView extends FrameLayout {
mMenuContainer.setAlpha(0f);
mSettingsButton.setAlpha(0f);
mDismissButton.setAlpha(0f);
- mEnterSplitButton.setAlpha(0f);
}
void pokeMenu() {
@@ -415,10 +374,7 @@ public class PipMenuView extends FrameLayout {
mSettingsButton.getAlpha(), 0f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 0f);
- ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
- mEnterSplitButton.getAlpha(), 0f);
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
- enterSplitAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -439,7 +395,7 @@ public class PipMenuView extends FrameLayout {
/**
* @return Estimated minimum {@link Size} to hold the actions.
- * See also {@link #updateActionViews(Rect)}
+ * See also {@link #updateActionViews(int, Rect)}
*/
Size getEstimatedMinMenuSize() {
final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
@@ -608,13 +564,6 @@ public class PipMenuView extends FrameLayout {
}
}
- private void enterSplit() {
- // Do not notify menu visibility when hiding the menu, the controller will do this when it
- // handles the message
- hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */,
- ANIM_TYPE_HIDE);
- }
-
private void showSettings() {
final Pair<ComponentName, Integer> topPipActivityInfo =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index c708b86e88c5..df67707e2014 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -42,6 +42,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -50,6 +51,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
+import java.util.Optional;
import java.util.function.Consumer;
/**
@@ -84,6 +86,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** Coordinator instance for resolving conflicts with other floating content. */
private FloatingContentCoordinator mFloatingContentCoordinator;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
/**
* PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
@@ -169,13 +174,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
mPipBoundsState = pipBoundsState;
mMenuController = menuController;
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -386,6 +393,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
}
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
private void movetoTarget(
float velocityX,
float velocityY,
@@ -591,6 +608,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
(int) toY + getBounds().height()));
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "startBoundsAnimator");
+ }
if (postBoundsUpdateCallback != null) {
mTemporaryBoundsPhysicsAnimator
.addUpdateListener(mResizePipUpdateListener)
@@ -633,6 +655,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
+ cleanUpHighPerfSessionMaybe();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index f175775ce8b2..89d3dd63a08e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,19 +15,14 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Looper;
import android.view.BatchedInputEventReceiver;
@@ -41,11 +36,11 @@ import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
@@ -53,7 +48,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -77,7 +71,6 @@ public class PipResizeGestureHandler {
private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
private final int mDisplayId;
private final ShellExecutor mMainExecutor;
- private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
private final PointF mDownSecondPoint = new PointF();
@@ -88,24 +81,15 @@ public class PipResizeGestureHandler {
private final Rect mLastResizeBounds = new Rect();
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
- private final Rect mDragCornerSize = new Rect();
- private final Rect mTmpTopLeftCorner = new Rect();
- private final Rect mTmpTopRightCorner = new Rect();
- private final Rect mTmpBottomLeftCorner = new Rect();
- private final Rect mTmpBottomRightCorner = new Rect();
- private final Rect mDisplayBounds = new Rect();
- private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private final Consumer<Rect> mUpdateResizeBoundsCallback;
- private int mDelta;
private float mTouchSlop;
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnablePinchResize;
- private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
@@ -116,6 +100,12 @@ public class PipResizeGestureHandler {
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
+ @Nullable
+ private final PipPerfHintController mPipPerfHintController;
+
+ @Nullable
+ private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
private int mCtrlType;
private int mOhmOffset;
@@ -123,19 +113,19 @@ public class PipResizeGestureHandler {
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
PipDismissTargetHandler pipDismissTargetHandler,
- Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+ Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mPipTouchState = pipTouchState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipDismissTargetHandler = pipDismissTargetHandler;
- mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
@@ -171,20 +161,9 @@ public class PipResizeGestureHandler {
}
private void reloadResources() {
- final Resources res = mContext.getResources();
- mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
- private void resetDragCorners() {
- mDragCornerSize.set(0, 0, mDelta, mDelta);
- mTmpTopLeftCorner.set(mDragCornerSize);
- mTmpTopRightCorner.set(mDragCornerSize);
- mTmpBottomLeftCorner.set(mDragCornerSize);
- mTmpBottomRightCorner.set(mDragCornerSize);
- }
-
private void disposeInputChannel() {
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
@@ -232,7 +211,7 @@ public class PipResizeGestureHandler {
@VisibleForTesting
void onInputEvent(InputEvent ev) {
- if (!mEnableDragCornerResize && !mEnablePinchResize) {
+ if (!mEnablePinchResize) {
// No need to handle anything if neither form of resizing is enabled.
return;
}
@@ -260,8 +239,6 @@ public class PipResizeGestureHandler {
if (mEnablePinchResize && mOngoingPinchToResize) {
onPinchResize(mv);
- } else if (mEnableDragCornerResize) {
- onDragCornerResize(mv);
}
}
}
@@ -273,48 +250,6 @@ public class PipResizeGestureHandler {
return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
}
- /**
- * Check whether the current x,y coordinate is within the region in which drag-resize should
- * start.
- * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
- * overlaps with the PIP window while the rest goes outside of the PIP window.
- * _ _ _ _
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- * | PIP |
- * | WINDOW |
- * _|_ _|_
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- */
- public boolean isWithinDragResizeRegion(int x, int y) {
- if (!mEnableDragCornerResize) {
- return false;
- }
-
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- if (currentPipBounds == null) {
- return false;
- }
- resetDragCorners();
- mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
- mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
-
- mTmpRegion.setEmpty();
- mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
-
- return mTmpRegion.contains(x, y);
- }
-
public boolean isUsingPinchToZoom() {
return mEnablePinchResize;
}
@@ -325,66 +260,31 @@ public class PipResizeGestureHandler {
public boolean willStartResizeGesture(MotionEvent ev) {
if (isInValidSysUiState()) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
- return true;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- onPinchResize(ev);
- mOngoingPinchToResize = mAllowGesture;
- return mAllowGesture;
- }
- break;
-
- default:
- break;
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
}
}
return false;
}
- private void setCtrlType(int x, int y) {
- final Rect currentPipBounds = mPipBoundsState.getBounds();
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+ private boolean isInValidSysUiState() {
+ return mIsSysUiStateValid;
+ }
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
- if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpBottomRightCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_BOTTOM;
- }
- if (mTmpBottomLeftCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_BOTTOM;
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
}
}
- private boolean isInValidSysUiState() {
- return mIsSysUiStateValid;
- }
-
@VisibleForTesting
void onPinchResize(MotionEvent ev) {
int action = ev.getActionMasked();
@@ -394,6 +294,7 @@ public class PipResizeGestureHandler {
mSecondIndex = -1;
mAllowGesture = false;
finishResize();
+ cleanUpHighPerfSessionMaybe();
}
if (ev.getPointerCount() != 2) {
@@ -415,6 +316,12 @@ public class PipResizeGestureHandler {
mLastPoint.set(mDownPoint);
mLastSecondPoint.set(mLastSecondPoint);
mLastResizeBounds.set(mDownBounds);
+
+ // start the high perf session as the second pointer gets detected
+ if (mPipPerfHintController != null) {
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "onPinchResize");
+ }
}
}
@@ -457,59 +364,6 @@ public class PipResizeGestureHandler {
}
}
- private void onDragCornerResize(MotionEvent ev) {
- int action = ev.getActionMasked();
- float x = ev.getX();
- float y = ev.getY() - mOhmOffset;
- if (action == MotionEvent.ACTION_DOWN) {
- mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
- if (mAllowGesture) {
- setCtrlType((int) x, (int) y);
- mDownPoint.set(x, y);
- mDownBounds.set(mPipBoundsState.getBounds());
- }
- } else if (mAllowGesture) {
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // We do not support multi touch for resizing via drag
- mAllowGesture = false;
- break;
- case MotionEvent.ACTION_MOVE:
- // Capture inputs
- if (!mThresholdCrossed
- && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
- mThresholdCrossed = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(x, y);
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
- false /* resize */);
- }
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
- mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
- mMinSize.y, mMaxSize, true,
- mDownBounds.width() > mDownBounds.height()));
- mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
- mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
- true /* useCurrentSize */);
- mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
- null);
- mPipBoundsState.setHasUserResizedPip(true);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- finishResize();
- break;
- }
- }
- }
-
private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
final int leftEdge = bounds.left;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
index 1a3cc8b1c1d2..fcac2c6b90ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
@@ -39,4 +39,9 @@ public abstract class PipTouchGesture {
public boolean onUp(PipTouchState touchState) {
return false;
}
+
+ /**
+ * Cleans up the high performance hint session if needed.
+ */
+ public void cleanUpHighPerfSessionMaybe() {}
}
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 452a41696fcf..c1adfffce074 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
@@ -25,8 +25,10 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
@@ -52,16 +54,18 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
@@ -82,6 +86,7 @@ public class PipTouchHandler {
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
private final ShellExecutor mMainExecutor;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -148,11 +153,6 @@ public class PipTouchHandler {
}
@Override
- public void onEnterSplit() {
- mMotionHelper.expandIntoSplit();
- }
-
- @Override
public void onPipDismiss() {
mTouchState.removeDoubleTapTimeoutCallback();
mMotionHelper.dismissPip();
@@ -176,9 +176,11 @@ public class PipTouchHandler {
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
mContext = context;
mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
@@ -211,8 +213,8 @@ public class PipTouchHandler {
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
- this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
- menuController, mainExecutor);
+ this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor, mPipPerfHintController);
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
@@ -465,17 +467,11 @@ public class PipTouchHandler {
}
private void updatePinchResizeSizeConstraints(float aspectRatio) {
- final int minWidth, minHeight, maxWidth, maxHeight;
-
- minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
- minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
- maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
- maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
-
- mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
- mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMinSize(minWidth, minHeight);
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
}
/**
@@ -524,6 +520,7 @@ public class PipTouchHandler {
}
if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mGesture.cleanUpHighPerfSessionMaybe();
mPipDismissTargetHandler.hideDismissTargetMaybe();
return true;
}
@@ -546,7 +543,7 @@ public class PipTouchHandler {
// Ignore the motion event When the entry animation is waiting to be started
if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) {
- ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
"%s: Waiting to start the entry animation, skip the motion event.", TAG);
return true;
}
@@ -793,12 +790,31 @@ public class PipTouchHandler {
private final PointF mDelta = new PointF();
private boolean mShouldHideMenuAfterFling;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ @Override
+ public void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
@Override
public void onDown(PipTouchState touchState) {
if (!touchState.isUserInteracting()) {
return;
}
+ if (mPipPerfHintController != null) {
+ // Cache the PiP high perf session to close it upon touch up.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+ }
+
Rect bounds = getPossiblyMotionBounds();
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
@@ -941,6 +957,7 @@ public class PipTouchHandler {
mTouchState.scheduleDoubleTapTimeoutCallback();
}
}
+ cleanUpHighPerfSessionMaybe();
return true;
}
@@ -1032,7 +1049,7 @@ public class PipTouchHandler {
}
final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
if (estimatedMinMenuSize == null) {
- ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to get estimated menu size", TAG);
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 5ee3734e371d..c5dc0edf1a4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -110,6 +110,7 @@ public class TvPipBoundsState extends PipBoundsState {
@Override
public void onConfigurationChanged() {
+ super.onConfigurationChanged();
updateDefaultGravity();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index cd3d38b6500c..3d286461ef79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -223,10 +223,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mShellController = shellController;
mDisplayController = displayController;
- DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
-
mTvPipBoundsState = tvPipBoundsState;
+ DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipDisplayLayoutState.setDisplayLayout(layout);
mPipDisplayLayoutState.setDisplayId(context.getDisplayId());
@@ -291,6 +290,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTvPipBoundsState.onConfigurationChanged();
+ mPipDisplayLayoutState.onConfigurationChanged();
int defaultGravityX = mTvPipBoundsState.getDefaultGravity()
& Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -299,6 +299,24 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onDensityOrFontScaleChanged()", TAG);
+ updatePinnedStackBounds();
+ mTvPipMenuController.reloadMenu();
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onDisplayConfigurationChanged(), displayId %d, saved display id %d",
+ TAG, displayId, mPipDisplayLayoutState.getDisplayId());
+ mPipDisplayLayoutState.setDisplayLayout(
+ new DisplayLayout(mContext, mContext.getDisplay()));
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ }
+
private void reloadResources() {
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index c6803f7beebd..62156fc7443b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -40,7 +40,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
@@ -63,6 +63,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private TvPipMenuView mPipMenuView;
private TvPipBackgroundView mPipBackgroundView;
+ private boolean mIsReloading;
+
@TvPipMenuMode
private int mCurrentMenuMode = MODE_NO_MENU;
@TvPipMenuMode
@@ -134,6 +136,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mTvPipActionsProvider = tvPipActionsProvider;
}
+ void reloadMenu() {
+ if (mLeash == null) {
+ return;
+ }
+ mPrevMenuMode = mCurrentMenuMode;
+ detachPipMenu();
+ mCurrentMenuMode = MODE_NO_MENU;
+ attachPipMenu(/* showEduText */ false);
+ mPipMenuView.onCloseEduTextAnimationEnd();
+ mIsReloading = true;
+ }
+
@Override
public void attach(SurfaceControl leash) {
if (mDelegate == null) {
@@ -141,10 +155,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
mLeash = leash;
- attachPipMenu();
+ attachPipMenu(/* showEduText */ true);
}
- private void attachPipMenu() {
+ private void attachPipMenu(boolean showEduText) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: attachPipMenu()", TAG);
@@ -155,13 +169,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
attachPipBackgroundView();
attachPipMenuView();
- int pipEduTextHeight = mContext.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
int pipMenuBorderWidth = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
-pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
+ if (showEduText) {
+ int pipEduTextHeight = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
+ } else {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ }
}
private void attachPipMenuView() {
@@ -223,6 +241,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mMainHandler.post(() -> {
if (mPipMenuView != null) {
mPipMenuView.onPipTransitionFinished(enterTransition);
+ if (mIsReloading) {
+ requestMenuMode(mPrevMenuMode);
+ mIsReloading = false;
+ }
}
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
index 202d36f0dfbd..adc03cf5c4d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.tv;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER;
-import static android.view.View.GONE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
@@ -36,7 +35,6 @@ import android.text.TextUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -267,7 +265,6 @@ class TvPipMenuEduTextDrawer extends FrameLayout {
}
public void onCloseEduTextAnimationEnd() {
- setVisibility(GONE);
mListener.onCloseEduTextAnimationEnd();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57439a59ccca..b259e8d584a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -89,6 +89,8 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
+ private final int mButtonStartEndOffset;
+
private final int mPipMenuFadeAnimationDuration;
private final int mResizeAnimationDuration;
@@ -147,6 +149,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mPipMenuOuterSpace = res.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
mPipMenuBorderWidth = res.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mArrowElevation = res.getDimensionPixelSize(R.dimen.pip_menu_arrow_elevation);
+ mButtonStartEndOffset = res.getDimensionPixelSize(R.dimen.pip_menu_button_start_end_offset);
initMoveArrows();
@@ -204,6 +207,16 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
v.setElevation(mArrowElevation);
}
+ private void setButtonPadding(boolean vertical) {
+ if (vertical) {
+ mActionButtonsRecyclerView.setPadding(
+ 0, mButtonStartEndOffset, 0, mButtonStartEndOffset);
+ } else {
+ mActionButtonsRecyclerView.setPadding(
+ mButtonStartEndOffset, 0, mButtonStartEndOffset, 0);
+ }
+ }
+
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
if (targetBounds == null) {
return;
@@ -244,6 +257,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
} else {
mButtonLayoutManager.setOrientation(vertical
? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ setButtonPadding(vertical);
}
}
@@ -262,9 +276,10 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.init();
}
+ boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
mButtonLayoutManager.setOrientation(
- mCurrentPipBounds.height() > mCurrentPipBounds.width()
- ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ vertical ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ setButtonPadding(vertical);
if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU
&& mActionButtonsRecyclerView.getAlpha() != 1f) {
mActionButtonsRecyclerView.animate()
@@ -468,6 +483,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
@Override
public void onCloseEduTextAnimationEnd() {
+ mEduTextDrawer.setVisibility(GONE);
mPipFrameView.setVisibility(GONE);
mEduTextContainer.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 1c94625ddde9..54e162bba2f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -54,6 +54,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
+ private static final String EXTRA_COMPONENT_NAME = "TvPipComponentName";
private final Context mContext;
private final PackageManager mPackageManager;
@@ -176,6 +177,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+ extras.putParcelable(EXTRA_COMPONENT_NAME, PipUtils.getTopPipActivity(mContext).first);
mNotificationBuilder.setExtras(extras);
PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 21223c9ac362..614ef2ab9831 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -28,10 +28,11 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -59,6 +60,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull TvPipTransition tvPipTransition,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -66,8 +68,8 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
- mainExecutor);
+ splitScreenOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
mTvPipTransition = tvPipTransition;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 571c839adf11..c2f4d72a1ddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -25,10 +25,10 @@ import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.transitTypeToString;
+import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP;
import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
@@ -71,9 +71,9 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
new file mode 100644
index 000000000000..24077a35d41c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -0,0 +1,277 @@
+/*
+ * 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.pip2;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
+ */
+public class PipSurfaceTransactionHelper {
+ /** for {@link #scale(SurfaceControl.Transaction, SurfaceControl, Rect, Rect)} operation */
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+ private final RectF mTmpSourceRectF = new RectF();
+ private final RectF mTmpDestinationRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
+
+ private int mCornerRadius;
+ private int mShadowRadius;
+
+ public PipSurfaceTransactionHelper(Context context) {
+ onDensityOrFontScaleChanged(context);
+ }
+
+ /**
+ * Called when display size or font size of settings changed
+ *
+ * @param context the current context
+ */
+ public void onDensityOrFontScaleChanged(Context context) {
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+ }
+
+ /**
+ * Operates the alpha on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
+ float alpha) {
+ tx.setAlpha(leash, alpha);
+ return this;
+ }
+
+ /**
+ * Operates the crop (and position) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds) {
+ return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, float degrees) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds, float degrees) {
+ mTmpSourceRectF.set(sourceBounds);
+ // We want the matrix to position the surface relative to the screen coordinates so offset
+ // the source to 0,0
+ mTmpSourceRectF.offsetTo(0, 0);
+ mTmpDestinationRectF.set(destinationBounds);
+ mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ mTmpTransform.postRotate(degrees,
+ mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY());
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
+ SurfaceControl leash, Rect sourceRectHint,
+ Rect sourceBounds, Rect destinationBounds, Rect insets,
+ boolean isInPipDirection, float fraction) {
+ mTmpDestinationRect.set(sourceBounds);
+ // Similar to {@link #scale}, we want to position the surface relative to the screen
+ // coordinates so offset the bounds to 0,0
+ mTmpDestinationRect.offsetTo(0, 0);
+ mTmpDestinationRect.inset(insets);
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
+ final float scale;
+ if (isInPipDirection
+ && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
+ // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
+ final float endScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceRectHint.width()
+ : (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - fraction) * startScale + fraction * endScale;
+ } else {
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
+ }
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
+ return this;
+ }
+
+ /**
+ * Operates the rotation according to the given degrees and scale (setMatrix) according to the
+ * source bounds and rotated destination bounds. The crop will be the unscaled source bounds.
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
+ SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets,
+ float degrees, float positionX, float positionY, boolean isExpanding,
+ boolean clockwise) {
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ final int srcW = mTmpDestinationRect.width();
+ final int srcH = mTmpDestinationRect.height();
+ final int destW = destinationBounds.width();
+ final int destH = destinationBounds.height();
+ // Scale by the short side so there won't be empty area if the aspect ratio of source and
+ // destination are different.
+ final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
+ final Rect crop = mTmpDestinationRect;
+ crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
+ : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+ // Inverse scale for crop to fit in screen coordinates.
+ crop.scale(1 / scale);
+ crop.offset(insets.left, insets.top);
+ if (isExpanding) {
+ // Expand bounds (shrink insets) in source orientation.
+ positionX -= insets.left * scale;
+ positionY -= insets.top * scale;
+ } else {
+ // Shrink bounds (expand insets) in destination orientation.
+ if (clockwise) {
+ positionX -= insets.top * scale;
+ positionY += insets.left * scale;
+ } else {
+ positionX += insets.top * scale;
+ positionY -= insets.left * scale;
+ }
+ }
+ mTmpTransform.setScale(scale, scale);
+ mTmpTransform.postRotate(degrees);
+ mTmpTransform.postTranslate(positionX, positionY);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
+ return this;
+ }
+
+ /**
+ * Resets the scale (setMatrix) on a given transaction and leash if there's any
+ *
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx,
+ SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ return this;
+ }
+
+ /**
+ * Operates the round corner radius on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ boolean applyCornerRadius) {
+ tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0);
+ return this;
+ }
+
+ /**
+ * Operates the round corner radius on a given transaction and leash, scaled by bounds
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect fromBounds, Rect toBounds) {
+ final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
+ / Math.hypot(toBounds.width(), toBounds.height()));
+ tx.setCornerRadius(leash, mCornerRadius * scale);
+ return this;
+ }
+
+ /**
+ * Operates the shadow radius on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper shadow(SurfaceControl.Transaction tx, SurfaceControl leash,
+ boolean applyShadowRadius) {
+ tx.setShadowRadius(leash, applyShadowRadius ? mShadowRadius : 0);
+ return this;
+ }
+
+ /**
+ * Interface to standardize {@link SurfaceControl.Transaction} generation across PiP.
+ */
+ public interface SurfaceControlTransactionFactory {
+ /**
+ * @return a new transaction to operate on.
+ */
+ SurfaceControl.Transaction getTransaction();
+ }
+
+ /**
+ * Implementation of {@link SurfaceControlTransactionFactory} that returns
+ * {@link SurfaceControl.Transaction} with VsyncId being set.
+ */
+ public static class VsyncSurfaceControlTransactionFactory
+ implements SurfaceControlTransactionFactory {
+ @Override
+ public SurfaceControl.Transaction getTransaction() {
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ return tx;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
new file mode 100644
index 000000000000..6e36a32ac931
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -0,0 +1,577 @@
+/*
+ * 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.pip2.phone;
+
+import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Size;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the PiP menu view which can show menu options or a scrim.
+ *
+ * The current media session provides actions whenever there are no valid actions provided by the
+ * current PiP activity. Otherwise, those actions always take precedence.
+ */
+public class PhonePipMenuController implements PipMenuController {
+
+ private static final String TAG = "PhonePipMenuController";
+ private static final boolean DEBUG = false;
+
+ public static final int MENU_STATE_NONE = 0;
+ public static final int MENU_STATE_FULL = 1;
+
+ /**
+ * A listener interface to receive notification on changes in PIP.
+ */
+ public interface Listener {
+ /**
+ * Called when the PIP menu visibility change has started.
+ *
+ * @param menuState the new, about-to-change state of the menu
+ * @param resize whether or not to resize the PiP with the state change
+ */
+ void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback);
+
+ /**
+ * Called when the PIP menu state has finished changing/animating.
+ *
+ * @param menuState the new state of the menu.
+ */
+ void onPipMenuStateChangeFinish(int menuState);
+
+ /**
+ * Called when the PIP requested to be expanded.
+ */
+ void onPipExpand();
+
+ /**
+ * Called when the PIP requested to be dismissed.
+ */
+ void onPipDismiss();
+
+ /**
+ * Called when the PIP requested to show the menu.
+ */
+ void onPipShowMenu();
+ }
+
+ private final Context mContext;
+ private final PipBoundsState mPipBoundsState;
+ private final PipMediaController mMediaController;
+ private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
+
+ private final ArrayList<Listener> mListeners = new ArrayList<>();
+ private final SystemWindows mSystemWindows;
+ private final PipUiEventLogger mPipUiEventLogger;
+
+ private List<RemoteAction> mAppActions;
+ private RemoteAction mCloseAction;
+ private List<RemoteAction> mMediaActions;
+
+ private int mMenuState;
+
+ private PipMenuView mPipMenuView;
+
+ private SurfaceControl mLeash;
+
+ private ActionListener mMediaActionListener = new ActionListener() {
+ @Override
+ public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
+ mMediaActions = new ArrayList<>(mediaActions);
+ updateMenuActions();
+ }
+ };
+
+ public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
+ PipMediaController mediaController, SystemWindows systemWindows,
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor mainExecutor, Handler mainHandler) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMediaController = mediaController;
+ mSystemWindows = systemWindows;
+ mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
+ mPipUiEventLogger = pipUiEventLogger;
+ }
+
+ public boolean isMenuVisible() {
+ return mPipMenuView != null && mMenuState != MENU_STATE_NONE;
+ }
+
+ /**
+ * Attach the menu when the PiP task first appears.
+ */
+ @Override
+ public void attach(SurfaceControl leash) {
+ mLeash = leash;
+ attachPipMenuView();
+ }
+
+ /**
+ * Detach the menu when the PiP task is gone.
+ */
+ @Override
+ public void detach() {
+ hideMenu();
+ detachPipMenuView();
+ mLeash = null;
+ }
+
+ void attachPipMenuView() {
+ // In case detach was not called (e.g. PIP unexpectedly closed)
+ if (mPipMenuView != null) {
+ detachPipMenuView();
+ }
+ mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
+ mPipUiEventLogger);
+ mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mLeash);
+ // make menu on top of the surface
+ t.setLayer(sc, Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ });
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mSystemWindows.addView(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
+ setShellRootAccessibilityWindow();
+
+ // Make sure the initial actions are set
+ updateMenuActions();
+ }
+
+ private void detachPipMenuView() {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
+ }
+
+ /**
+ * Updates the layout parameters of the menu.
+ * @param destinationBounds New Menu bounds.
+ */
+ @Override
+ public void updateMenuBounds(Rect destinationBounds) {
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, destinationBounds.width(),
+ destinationBounds.height()));
+ updateMenuLayout(destinationBounds);
+ }
+
+ /**
+ * Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some
+ * reason (ie. the window isn't ready yet, thus {@link ViewRootImpl} is
+ * {@code null}), it will get the leash that the WindowlessWM has assigned to it.
+ */
+ public SurfaceControl getSurfaceControl() {
+ return mSystemWindows.getViewSurface(mPipMenuView);
+ }
+
+ /**
+ * Adds a new menu activity listener.
+ */
+ public void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ @Nullable
+ Size getEstimatedMinMenuSize() {
+ return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
+ }
+
+ /**
+ * When other components requests the menu controller directly to show the menu, we must
+ * first fire off the request to the other listeners who will then propagate the call
+ * back to the controller with the right parameters.
+ */
+ @Override
+ public void showMenu() {
+ mListeners.forEach(Listener::onPipShowMenu);
+ }
+
+ /**
+ * Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
+ * upon PiP window transition is finished.
+ */
+ public void showMenuWithPossibleDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean showResizeHandle) {
+ if (willResizeMenu) {
+ // hide all visible controls including close button and etc. first, this is to ensure
+ // menu is totally invisible during the transition to eliminate unpleasant artifacts
+ fadeOutMenu();
+ }
+ showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
+ willResizeMenu /* withDelay=willResizeMenu here */, showResizeHandle);
+ }
+
+ /**
+ * Shows the menu activity immediately.
+ */
+ public void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean showResizeHandle) {
+ showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
+ false /* withDelay */, showResizeHandle);
+ }
+
+ private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " allowMenuTimeout=%s"
+ + " willResizeMenu=%s"
+ + " withDelay=%s"
+ + " showResizeHandle=%s"
+ + " callers=\n%s", TAG, menuState, isMenuVisible(), allowMenuTimeout,
+ willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // Sync the menu bounds before showing it in case it is out of sync.
+ movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
+ updateMenuBounds(stackBounds);
+
+ mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
+ showResizeHandle);
+ }
+
+ /**
+ * Move the PiP menu, which does a translation and possibly a scale transformation.
+ */
+ @Override
+ public void movePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds, float alpha) {
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
+ if (pipLeash != null && t != null) {
+ t.apply();
+ }
+ }
+
+ /**
+ * Does an immediate window crop of the PiP menu.
+ */
+ @Override
+ public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
+ if (pipLeash != null && t != null) {
+ t.apply();
+ }
+ }
+
+ private boolean checkPipMenuState() {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Pokes the menu, indicating that the user is interacting with it.
+ */
+ public void pokeMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: pokeMenu() isMenuVisible=%b", TAG, isMenuVisible);
+ }
+ if (isMenuVisible) {
+ mPipMenuView.pokeMenu();
+ }
+ }
+
+ private void fadeOutMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: fadeOutMenu() isMenuVisible=%b", TAG, isMenuVisible);
+ }
+ if (isMenuVisible) {
+ mPipMenuView.fadeOutMenu();
+ }
+ }
+
+ /**
+ * Hides the menu view.
+ */
+ public void hideMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (isMenuVisible) {
+ mPipMenuView.hideMenu();
+ }
+ }
+
+ /**
+ * Hides the menu view.
+ *
+ * @param animationType the animation type to use upon hiding the menu
+ * @param resize whether or not to resize the PiP with the state change
+ */
+ public void hideMenu(@PipMenuView.AnimationType int animationType, boolean resize) {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " animationType=%s"
+ + " resize=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ animationType, resize,
+ Debug.getCallers(5, " "));
+ }
+ if (isMenuVisible) {
+ mPipMenuView.hideMenu(resize, animationType);
+ }
+ }
+
+ /**
+ * Hides the menu activity.
+ */
+ public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) {
+ if (isMenuVisible()) {
+ // If the menu is visible in either the closed or full state, then hide the menu and
+ // trigger the animation trigger afterwards
+ if (onStartCallback != null) {
+ onStartCallback.run();
+ }
+ mPipMenuView.hideMenu(onEndCallback);
+ }
+ }
+
+ /**
+ * Sets the menu actions to the actions provided by the current PiP menu.
+ */
+ @Override
+ public void setAppActions(List<RemoteAction> appActions,
+ RemoteAction closeAction) {
+ mAppActions = appActions;
+ mCloseAction = closeAction;
+ updateMenuActions();
+ }
+
+ void onPipExpand() {
+ mListeners.forEach(Listener::onPipExpand);
+ }
+
+ void onPipDismiss() {
+ mListeners.forEach(Listener::onPipDismiss);
+ }
+
+ /**
+ * @return the best set of actions to show in the PiP menu.
+ */
+ private List<RemoteAction> resolveMenuActions() {
+ if (isValidActions(mAppActions)) {
+ return mAppActions;
+ }
+ return mMediaActions;
+ }
+
+ /**
+ * Updates the PiP menu with the best set of actions provided.
+ */
+ private void updateMenuActions() {
+ if (mPipMenuView != null) {
+ mPipMenuView.setActions(mPipBoundsState.getBounds(),
+ resolveMenuActions(), mCloseAction);
+ }
+ }
+
+ /**
+ * Returns whether the set of actions are valid.
+ */
+ private static boolean isValidActions(List<?> actions) {
+ return actions != null && actions.size() > 0;
+ }
+
+ /**
+ * Handles changes in menu visibility.
+ */
+ void onMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMenuStateChangeStart() mMenuState=%s"
+ + " menuState=%s resize=%s"
+ + " callers=\n%s", TAG, mMenuState, menuState, resize,
+ Debug.getCallers(5, " "));
+ }
+
+ if (menuState != mMenuState) {
+ mListeners.forEach(l -> l.onPipMenuStateChangeStart(menuState, resize, callback));
+ if (menuState == MENU_STATE_FULL) {
+ // Once visible, start listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.addActionListener(mMediaActionListener);
+ } else {
+ // Once hidden, stop listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.removeActionListener(mMediaActionListener);
+ }
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mSystemWindows.getFocusGrantToken(mPipMenuView),
+ menuState != MENU_STATE_NONE /* grantFocus */);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus as menu appears/disappears, %s", TAG, e);
+ }
+ }
+ }
+
+ void onMenuStateChangeFinish(int menuState) {
+ if (menuState != mMenuState) {
+ mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState));
+ }
+ mMenuState = menuState;
+ setShellRootAccessibilityWindow();
+ }
+
+ private void setShellRootAccessibilityWindow() {
+ switch (mMenuState) {
+ case MENU_STATE_NONE:
+ mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP, null);
+ break;
+ default:
+ mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP,
+ mPipMenuView);
+ break;
+ }
+ }
+
+ /**
+ * Handles a pointer event sent from pip input consumer.
+ */
+ void handlePointerEvent(MotionEvent ev) {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ if (ev.isTouchEvent()) {
+ mPipMenuView.dispatchTouchEvent(ev);
+ } else {
+ mPipMenuView.dispatchGenericMotionEvent(ev);
+ }
+ }
+
+ /**
+ * Tell the PIP Menu to recalculate its layout given its current position on the display.
+ */
+ public void updateMenuLayout(Rect bounds) {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuLayout() state=%s"
+ + " isMenuVisible=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ Debug.getCallers(5, " "));
+ }
+ if (isMenuVisible) {
+ mPipMenuView.updateMenuLayout(bounds);
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mMenuState=" + mMenuState);
+ pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView);
+ pw.println(innerPrefix + "mListeners=" + mListeners.size());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 186cb615f4ec..e73a85003881 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -18,14 +18,31 @@ package com.android.wm.shell.pip2.phone;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -37,32 +54,54 @@ import com.android.wm.shell.sysui.ShellInit;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
- DisplayController.OnDisplaysChangedListener {
+ DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
private Context mContext;
private ShellController mShellController;
private DisplayController mDisplayController;
private DisplayInsetsController mDisplayInsetsController;
+ private PipBoundsState mPipBoundsState;
+ private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipScheduler mPipScheduler;
+ private ShellExecutor mMainExecutor;
private PipController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipScheduler = pipScheduler;
+ mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void onInit() {
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
@@ -80,6 +119,10 @@ public class PipController implements ConfigurationChangeListener,
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+
+ // Allow other outside processes to bind to PiP controller using the key below.
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
}
/**
@@ -90,16 +133,24 @@ public class PipController implements ConfigurationChangeListener,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
return null;
}
return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
+ displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
+ pipScheduler, mainExecutor);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
+ }
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -130,4 +181,86 @@ public class PipController implements ConfigurationChangeListener,
private void onDisplayChanged(DisplayLayout layout) {
mPipDisplayLayoutState.setDisplayLayout(layout);
}
+
+ private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, Rect hotseatKeepClearArea) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "getSwipePipToHomeBounds: %s", componentName);
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
+ mPipBoundsAlgorithm);
+ return mPipBoundsAlgorithm.getEntryDestinationBounds();
+ }
+
+ private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onSwipePipToHomeAnimationStart: %s", componentName);
+ mPipScheduler.setInSwipePipToHomeTransition(true);
+ // TODO: cache the overlay if provided for reparenting later.
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
+ private PipController mController;
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ Rect keepClearArea) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, keepClearArea);
+ }, true /* blocking */);
+ return result[0];
+ }
+
+ @Override
+ public void stopSwipePipToHome(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> controller.onSwipePipToHomeAnimationStart(
+ taskId, componentName, destinationBounds, overlay, appBounds));
+ }
+
+ @Override
+ public void abortSwipePipToHome(int taskId, ComponentName componentName) {}
+
+ @Override
+ public void setShelfHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {}
+
+ @Override
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ // TODO: set a proper animation listener to update the Launcher state as needed.
+ }
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {}
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java
new file mode 100644
index 000000000000..7252675dc52d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java
@@ -0,0 +1,56 @@
+/*
+ * 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.pip2.phone;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container layout wraps single action image view drawn in PiP menu and can restrict the size of
+ * action image view (see pip_menu_action.xml).
+ */
+public class PipMenuActionView extends FrameLayout {
+ private ImageView mImageView;
+ private View mCustomCloseBackground;
+
+ public PipMenuActionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = findViewById(R.id.image);
+ mCustomCloseBackground = findViewById(R.id.custom_close_bg);
+ }
+
+ /** pass through to internal {@link #mImageView} */
+ public void setImageDrawable(Drawable drawable) {
+ mImageView.setImageDrawable(drawable);
+ }
+
+ /** pass through to internal {@link #mCustomCloseBackground} */
+ public void setCustomCloseBackgroundVisibility(@Visibility int visibility) {
+ mCustomCloseBackground.setVisibility(visibility);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
new file mode 100644
index 000000000000..ecb6ad690e26
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
@@ -0,0 +1,71 @@
+/*
+ * 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.pip2.phone;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * Helper class to calculate and place the menu icons on the PIP Menu.
+ */
+public class PipMenuIconsAlgorithm {
+
+ private static final String TAG = "PipMenuIconsAlgorithm";
+
+ protected ViewGroup mViewRoot;
+ protected ViewGroup mTopEndContainer;
+ protected View mDragHandle;
+ protected View mSettingsButton;
+ protected View mDismissButton;
+
+ protected PipMenuIconsAlgorithm(Context context) {
+ }
+
+ /**
+ * Bind the necessary views.
+ */
+ public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
+ View settingsButton, View dismissButton) {
+ mViewRoot = viewRoot;
+ mTopEndContainer = topEndContainer;
+ mDragHandle = dragHandle;
+ mSettingsButton = settingsButton;
+ mDismissButton = dismissButton;
+ }
+
+ /**
+ * Updates the position of the drag handle based on where the PIP window is on the screen.
+ */
+ public void onBoundsChanged(Rect bounds) {
+ // On phones, the menu icons are always static and will never move based on the PIP window
+ // position. No need to do anything here.
+ }
+
+ /**
+ * Set the gravity on the given view.
+ */
+ protected static void setLayoutGravity(View v, int gravity) {
+ if (v.getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
+ params.gravity = gravity;
+ v.setLayoutParams(params);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
new file mode 100644
index 000000000000..42b8e9f5a3ad
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
@@ -0,0 +1,600 @@
+/*
+ * 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.pip2.phone;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Pair;
+import android.util.Size;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Translucent window that gets started on top of a task in PIP to allow the user to control it.
+ */
+public class PipMenuView extends FrameLayout {
+
+ private static final String TAG = "PipMenuView";
+
+ private static final int ANIMATION_NONE_DURATION_MS = 0;
+ private static final int ANIMATION_HIDE_DURATION_MS = 125;
+
+ /** No animation performed during menu hide. */
+ public static final int ANIM_TYPE_NONE = 0;
+ /** Fade out the menu until it's invisible. Used when the PIP window remains visible. */
+ public static final int ANIM_TYPE_HIDE = 1;
+ /** Fade out the menu in sync with the PIP window. */
+ public static final int ANIM_TYPE_DISMISS = 2;
+
+ @IntDef(prefix = { "ANIM_TYPE_" }, value = {
+ ANIM_TYPE_NONE,
+ ANIM_TYPE_HIDE,
+ ANIM_TYPE_DISMISS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnimationType {}
+
+ private static final int INITIAL_DISMISS_DELAY = 3500;
+ private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
+ private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
+
+ private static final float MENU_BACKGROUND_ALPHA = 0.54f;
+ private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
+ private int mMenuState;
+ private boolean mAllowMenuTimeout = true;
+ private boolean mAllowTouches = true;
+ private int mDismissFadeOutDurationMs;
+ private final List<RemoteAction> mActions = new ArrayList<>();
+ private RemoteAction mCloseAction;
+
+ private AccessibilityManager mAccessibilityManager;
+ private Drawable mBackgroundDrawable;
+ private View mMenuContainer;
+ private LinearLayout mActionsGroup;
+ private int mBetweenActionPaddingLand;
+
+ private AnimatorSet mMenuContainerAnimator;
+ private final PhonePipMenuController mController;
+ private final PipUiEventLogger mPipUiEventLogger;
+
+ private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float alpha = (float) animation.getAnimatedValue();
+ mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255));
+ }
+ };
+
+ private ShellExecutor mMainExecutor;
+ private Handler mMainHandler;
+
+ /**
+ * Whether the most recent showing of the menu caused a PIP resize, such as when PIP is too
+ * small and it is resized on menu show to fit the actions.
+ */
+ private boolean mDidLastShowMenuResize;
+ private final Runnable mHideMenuRunnable = this::hideMenu;
+
+ protected View mViewRoot;
+ protected View mSettingsButton;
+ protected View mDismissButton;
+ protected View mTopEndContainer;
+ protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+
+ // How long the shell will wait for the app to close the PiP if a custom action is set.
+ private final int mPipForceCloseDelay;
+
+ public PipMenuView(Context context, PhonePipMenuController controller,
+ ShellExecutor mainExecutor, Handler mainHandler, PipUiEventLogger pipUiEventLogger) {
+ super(context, null, 0);
+ mContext = context;
+ mController = controller;
+ mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
+ mPipUiEventLogger = pipUiEventLogger;
+
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ inflate(context, R.layout.pip_menu, this);
+
+ mPipForceCloseDelay = context.getResources().getInteger(
+ R.integer.config_pipForceCloseDelay);
+
+ mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background);
+ mBackgroundDrawable.setAlpha(0);
+ mViewRoot = findViewById(R.id.background);
+ mViewRoot.setBackground(mBackgroundDrawable);
+ mMenuContainer = findViewById(R.id.menu_container);
+ mMenuContainer.setAlpha(0);
+ mTopEndContainer = findViewById(R.id.top_end_container);
+ mSettingsButton = findViewById(R.id.settings);
+ mSettingsButton.setAlpha(0);
+ mSettingsButton.setOnClickListener((v) -> {
+ if (v.getAlpha() != 0) {
+ showSettings();
+ }
+ });
+ mDismissButton = findViewById(R.id.dismiss);
+ mDismissButton.setAlpha(0);
+ mDismissButton.setOnClickListener(v -> dismissPip());
+ findViewById(R.id.expand_button).setOnClickListener(v -> {
+ if (mMenuContainer.getAlpha() != 0) {
+ expandPip();
+ }
+ });
+
+ findViewById(R.id.resize_handle).setAlpha(0);
+
+ mActionsGroup = findViewById(R.id.actions_group);
+ mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
+ R.dimen.pip_between_action_padding_land);
+ mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
+ mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
+ findViewById(R.id.resize_handle), mSettingsButton, mDismissButton);
+ mDismissFadeOutDurationMs = context.getResources()
+ .getInteger(R.integer.config_pipExitAnimationDuration);
+
+ initAccessibility();
+ }
+
+ private void initAccessibility() {
+ this.setAccessibilityDelegate(new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ String label = getResources().getString(R.string.pip_menu_title);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_CLICK && mMenuState != MENU_STATE_FULL) {
+ mController.showMenu();
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ hideMenu();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (!mAllowTouches) {
+ return false;
+ }
+
+ if (mAllowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (mAllowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
+ mAllowMenuTimeout = allowMenuTimeout;
+ mDidLastShowMenuResize = resizeMenuOnShow;
+ if (mMenuState != menuState) {
+ // Disallow touches if the menu needs to resize while showing, and we are transitioning
+ // to/from a full menu state.
+ boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow
+ && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+ mAllowTouches = !disallowTouchesUntilAnimationEnd;
+ cancelDelayedHide();
+ if (mMenuContainerAnimator != null) {
+ mMenuContainerAnimator.cancel();
+ }
+ mMenuContainerAnimator = new AnimatorSet();
+ ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+ mMenuContainer.getAlpha(), 1f);
+ menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 1f);
+ ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+ mDismissButton.getAlpha(), 1f);
+ if (menuState == MENU_STATE_FULL) {
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
+ }
+ mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
+ mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
+ mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAllowTouches = true;
+ notifyMenuStateChangeFinish(menuState);
+ if (allowMenuTimeout) {
+ repostDelayedHide(INITIAL_DISMISS_DELAY);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAllowTouches = true;
+ }
+ });
+ if (withDelay) {
+ // starts the menu container animation after window expansion is completed
+ notifyMenuStateChangeStart(menuState, resizeMenuOnShow, () -> {
+ if (mMenuContainerAnimator == null) {
+ return;
+ }
+ mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
+ setVisibility(VISIBLE);
+ mMenuContainerAnimator.start();
+ });
+ } else {
+ notifyMenuStateChangeStart(menuState, resizeMenuOnShow, null);
+ setVisibility(VISIBLE);
+ mMenuContainerAnimator.start();
+ }
+ updateActionViews(menuState, stackBounds);
+ } else {
+ // If we are already visible, then just start the delayed dismiss and unregister any
+ // existing input consumers from the previous drag
+ if (allowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+ }
+ }
+
+ /**
+ * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
+ * and instead, it fades out the controls by setting the alpha to 0 directly without menu
+ * visibility callbacks invoked.
+ */
+ void fadeOutMenu() {
+ mMenuContainer.setAlpha(0f);
+ mSettingsButton.setAlpha(0f);
+ mDismissButton.setAlpha(0f);
+ }
+
+ void pokeMenu() {
+ cancelDelayedHide();
+ }
+
+ void updateMenuLayout(Rect bounds) {
+ mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
+ }
+
+ void hideMenu() {
+ hideMenu(null);
+ }
+
+ void hideMenu(Runnable animationEndCallback) {
+ hideMenu(animationEndCallback, true /* notifyMenuVisibility */, mDidLastShowMenuResize,
+ ANIM_TYPE_HIDE);
+ }
+
+ void hideMenu(boolean resize, @AnimationType int animationType) {
+ hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */, resize,
+ animationType);
+ }
+
+ void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
+ boolean resize, @AnimationType int animationType) {
+ if (mMenuState != MENU_STATE_NONE) {
+ cancelDelayedHide();
+ if (notifyMenuVisibility) {
+ notifyMenuStateChangeStart(MENU_STATE_NONE, resize, null);
+ }
+ mMenuContainerAnimator = new AnimatorSet();
+ ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+ mMenuContainer.getAlpha(), 0f);
+ menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 0f);
+ ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+ mDismissButton.getAlpha(), 0f);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
+ mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+ mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
+ mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setVisibility(GONE);
+ if (notifyMenuVisibility) {
+ notifyMenuStateChangeFinish(MENU_STATE_NONE);
+ }
+ if (animationFinishedRunnable != null) {
+ animationFinishedRunnable.run();
+ }
+ }
+ });
+ mMenuContainerAnimator.start();
+ }
+ }
+
+ /**
+ * @return Estimated minimum {@link Size} to hold the actions.
+ * See also {@link #updateActionViews(int, Rect)}
+ */
+ Size getEstimatedMinMenuSize() {
+ final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+ // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+ // on the top action container.
+ final int width = Math.max(2, mActions.size()) * pipActionSize;
+ final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+ + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+ + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
+ return new Size(width, height);
+ }
+
+ void setActions(Rect stackBounds, @Nullable List<RemoteAction> actions,
+ @Nullable RemoteAction closeAction) {
+ mActions.clear();
+ if (actions != null && !actions.isEmpty()) {
+ mActions.addAll(actions);
+ }
+ mCloseAction = closeAction;
+ if (mMenuState == MENU_STATE_FULL) {
+ updateActionViews(mMenuState, stackBounds);
+ }
+ }
+
+ private void updateActionViews(int menuState, Rect stackBounds) {
+ ViewGroup expandContainer = findViewById(R.id.expand_container);
+ ViewGroup actionsContainer = findViewById(R.id.actions_container);
+ actionsContainer.setOnTouchListener((v, ev) -> {
+ // Do nothing, prevent click through to parent
+ return true;
+ });
+
+ // Update the expand button only if it should show with the menu
+ expandContainer.setVisibility(menuState == MENU_STATE_FULL
+ ? View.VISIBLE
+ : View.INVISIBLE);
+
+ LayoutParams expandedLp =
+ (LayoutParams) expandContainer.getLayoutParams();
+ if (mActions.isEmpty() || menuState == MENU_STATE_NONE) {
+ actionsContainer.setVisibility(View.INVISIBLE);
+
+ // Update the expand container margin to adjust the center of the expand button to
+ // account for the existence of the action container
+ expandedLp.topMargin = 0;
+ expandedLp.bottomMargin = 0;
+ } else {
+ actionsContainer.setVisibility(View.VISIBLE);
+ if (mActionsGroup != null) {
+ // Ensure we have as many buttons as actions
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ while (mActionsGroup.getChildCount() < mActions.size()) {
+ final PipMenuActionView actionView = (PipMenuActionView) inflater.inflate(
+ R.layout.pip_menu_action, mActionsGroup, false);
+ mActionsGroup.addView(actionView);
+ }
+
+ // Update the visibility of all views
+ for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+ mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ // Recreate the layout
+ final boolean isLandscapePip = stackBounds != null
+ && (stackBounds.width() > stackBounds.height());
+ for (int i = 0; i < mActions.size(); i++) {
+ final RemoteAction action = mActions.get(i);
+ final PipMenuActionView actionView =
+ (PipMenuActionView) mActionsGroup.getChildAt(i);
+ final boolean isCloseAction = mCloseAction != null && Objects.equals(
+ mCloseAction.getActionIntent(), action.getActionIntent());
+
+ final int iconType = action.getIcon().getType();
+ if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // Disallow loading icon from content URI
+ actionView.setImageDrawable(null);
+ } else {
+ // TODO: Check if the action drawable has changed before we reload it
+ action.getIcon().loadDrawableAsync(mContext, d -> {
+ if (d != null) {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }
+ }, mMainHandler);
+ }
+ actionView.setCustomCloseBackgroundVisibility(
+ isCloseAction ? View.VISIBLE : View.GONE);
+ actionView.setContentDescription(action.getContentDescription());
+ if (action.isEnabled()) {
+ actionView.setOnClickListener(
+ v -> onActionViewClicked(action.getActionIntent(), isCloseAction));
+ }
+ actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+
+ // Update the margin between actions
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ actionView.getLayoutParams();
+ lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
+ }
+ }
+
+ // Update the expand container margin to adjust the center of the expand button to
+ // account for the existence of the action container
+ expandedLp.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.pip_action_padding);
+ expandedLp.bottomMargin = getResources().getDimensionPixelSize(
+ R.dimen.pip_expand_container_edge_margin);
+ }
+ expandContainer.requestLayout();
+ }
+
+ private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ mController.onMenuStateChangeStart(menuState, resize, callback);
+ }
+
+ private void notifyMenuStateChangeFinish(int menuState) {
+ mMenuState = menuState;
+ mController.onMenuStateChangeFinish(menuState);
+ }
+
+ private void expandPip() {
+ // Do not notify menu visibility when hiding the menu, the controller will do this when it
+ // handles the message
+ hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */,
+ ANIM_TYPE_HIDE);
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
+ }
+
+ private void dismissPip() {
+ if (mMenuState != MENU_STATE_NONE) {
+ // Do not call hideMenu() directly. Instead, let the menu controller handle it just as
+ // any other dismissal that will update the touch state and fade out the PIP task
+ // and the menu view at the same time.
+ mController.onPipDismiss();
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
+ }
+ }
+
+ /**
+ * Execute the {@link PendingIntent} attached to the {@link PipMenuActionView}.
+ * If the given {@link PendingIntent} matches {@link #mCloseAction}, we need to make sure
+ * the PiP is removed after a certain timeout in case the app does not respond in a
+ * timely manner.
+ */
+ private void onActionViewClicked(@NonNull PendingIntent intent, boolean isCloseAction) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ if (isCloseAction) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_CUSTOM_CLOSE);
+ mAllowTouches = false;
+ mMainExecutor.executeDelayed(() -> {
+ hideMenu();
+ // TODO: it's unsafe to call onPipDismiss with a delay here since
+ // we may have a different PiP by the time this runnable is executed.
+ mController.onPipDismiss();
+ mAllowTouches = true;
+ }, mPipForceCloseDelay);
+ }
+ }
+
+ private void showSettings() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPipActivity(mContext);
+ if (topPipActivityInfo.first != null) {
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second));
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_SETTINGS);
+ }
+ }
+
+ private void cancelDelayedHide() {
+ mMainExecutor.removeCallbacks(mHideMenuRunnable);
+ }
+
+ private void repostDelayedHide(int delay) {
+ int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
+ FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
+ mMainExecutor.removeCallbacks(mHideMenuRunnable);
+ mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
+ }
+
+ private long getFadeOutDuration(@AnimationType int animationType) {
+ switch (animationType) {
+ case ANIM_TYPE_NONE:
+ return ANIMATION_NONE_DURATION_MS;
+ case ANIM_TYPE_HIDE:
+ return ANIMATION_HIDE_DURATION_MS;
+ case ANIM_TYPE_DISMISS:
+ return mDismissFadeOutDurationMs;
+ default:
+ throw new IllegalStateException("Invalid animation type " + animationType);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0448d94669ce..895c793007a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -36,6 +38,10 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -57,14 +63,41 @@ public class PipScheduler {
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
+ /**
+ * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+ * This is used for a broadcast receiver to resolve intents. This should be removed once
+ * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+ */
+ private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+ private static final int PIP_DOUBLE_TAP = 1;
+
+ @IntDef(value = {
+ PIP_EXIT_VIA_EXPAND_CODE,
+ PIP_DOUBLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PipUserJourneyCode {}
+
/**
- * A temporary broadcast receiver to initiate exit PiP via expand.
- * This will later be modified to be triggered by the PiP menu.
+ * A temporary broadcast receiver to initiate PiP CUJs.
*/
private class PipSchedulerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- scheduleExitPipViaExpand();
+ int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+ switch (userJourneyCode) {
+ case PIP_EXIT_VIA_EXPAND_CODE:
+ scheduleExitPipViaExpand();
+ break;
+ case PIP_DOUBLE_TAP:
+ scheduleDoubleTapToResize();
+ break;
+ default:
+ throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+ }
}
}
@@ -96,7 +129,7 @@ public class PipScheduler {
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTaskToken == null || mPinnedTaskLeash == null) {
+ if (mPipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -121,6 +154,31 @@ public class PipScheduler {
}
}
+ /**
+ * Schedules resize PiP via double tap.
+ */
+ public void scheduleDoubleTapToResize() {}
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+ if (mPipTaskToken == null) {
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+ }
+
+ void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
+ mInSwipePipToHomeTransition = true;
+ }
+
+ boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
void onExitPip() {
mPipTaskToken = null;
mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 6200ea583a48..dfb04758c851 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -18,12 +18,16 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -34,30 +38,39 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Implementation of transitions for PiP on phone.
*/
public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ private final Context mContext;
private final PipScheduler mPipScheduler;
@Nullable
private WindowContainerToken mPipTaskToken;
@Nullable
- private IBinder mAutoEnterButtonNavTransition;
+ private IBinder mEnterTransition;
@Nullable
private IBinder mExitViaExpandTransition;
+ @Nullable
+ private IBinder mResizeTransition;
+
+ private Consumer<Rect> mFinishResizeCallback;
public PipTransition(
+ Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
@@ -68,6 +81,7 @@ public class PipTransition extends PipTransitionController {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
+ mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
}
@@ -81,7 +95,7 @@ public class PipTransition extends PipTransitionController {
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
- @android.annotation.Nullable Rect destinationBounds) {
+ @Nullable Rect destinationBounds) {
if (out == null) {
return;
}
@@ -91,12 +105,22 @@ public class PipTransition extends PipTransitionController {
}
}
+ @Override
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ if (wct == null) {
+ return;
+ }
+ mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mFinishResizeCallback = onFinishResizeCallback;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (isAutoEnterInButtonNavigation(request)) {
- mAutoEnterButtonNavTransition = transition;
+ if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
+ mEnterTransition = transition;
return getEnterPipTransaction(transition, request);
}
return null;
@@ -105,9 +129,9 @@ public class PipTransition extends PipTransitionController {
@Override
public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
@NonNull WindowContainerTransaction outWct) {
- if (isAutoEnterInButtonNavigation(request)) {
+ if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
- mAutoEnterButtonNavTransition = transition;
+ mEnterTransition = transition;
}
}
@@ -120,6 +144,151 @@ public class PipTransition extends PipTransitionController {
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition == mEnterTransition) {
+ mEnterTransition = null;
+ if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If this is the second transition as a part of swipe PiP to home cuj,
+ // handle this transition as a special case with no-op animation.
+ return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+ if (isLegacyEnter(info)) {
+ // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
+ // then we should run an ALPHA type (cross-fade) animation.
+ return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+ return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ } else if (transition == mExitViaExpandTransition) {
+ mExitViaExpandTransition = null;
+ return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+ } else if (transition == mResizeTransition) {
+ mResizeTransition = null;
+ return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
+ }
+ return false;
+ }
+
+ private boolean startResizeAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ SurfaceControl pipLeash = pipChange.getLeash();
+ Rect destinationBounds = pipChange.getEndAbsBounds();
+
+ // Even though the final bounds and crop are applied with finishTransaction since
+ // this is a visible change, we still need to handle the app draw coming in. Snapshot
+ // covering app draw during collection will be removed by startTransaction. So we make
+ // the crop equal to the final bounds and then scale the leash back to starting bounds.
+ startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+ pipChange.getEndAbsBounds().height());
+ startTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+ startTransaction.apply();
+
+ finishTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+ // We are done with the transition, but will continue animating leash to final bounds.
+ finishCallback.onTransitionFinished(null);
+
+ // Animate the pip leash with the new buffer
+ final int duration = mContext.getResources().getInteger(
+ R.integer.config_pipResizeAnimationDuration);
+ // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+ startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+ return true;
+ }
+
+ private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipScheduler.setInSwipePipToHomeTransition(false);
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startExpandAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+
+ @Nullable
+ private TransitionInfo.Change getPipChange(TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
@@ -153,48 +322,24 @@ public class PipTransition extends PipTransitionController {
&& pipTask.pictureInPictureParams.isAutoEnterEnabled();
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition == mAutoEnterButtonNavTransition) {
- mAutoEnterButtonNavTransition = null;
- TransitionInfo.Change pipChange = getPipChange(info);
- if (pipChange == null) {
- return false;
- }
- mPipTaskToken = pipChange.getContainer();
-
- // cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
- mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
-
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- return true;
- } else if (transition == mExitViaExpandTransition) {
- mExitViaExpandTransition = null;
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- onExitPip();
- return true;
- }
- return false;
+ private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+ return requestInfo.getType() == TRANSIT_PIP;
}
- @Nullable
- private TransitionInfo.Change getPipChange(TransitionInfo info) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
- return change;
- }
- }
- return null;
+ private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ // If the only change in the changes list is a TO_FRONT mode PiP task,
+ // then this is legacy-enter PiP.
+ return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+ && info.getChanges().size() == 1;
}
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {}
+
private void onExitPip() {
mPipTaskToken = null;
mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 05e4af3302af..ad29d15019c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,6 +26,8 @@ import com.android.internal.protolog.common.IProtoLogGroup;
public enum ShellProtoLogGroup implements IProtoLogGroup {
// NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
// with those in the framework ProtoLogGroup
+ WM_SHELL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
WM_SHELL_INIT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
deleted file mode 100644
index 93ffb3dc8115..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ /dev/null
@@ -1,120 +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.protolog;
-
-import android.annotation.Nullable;
-
-import com.android.internal.protolog.BaseProtoLogImpl;
-import com.android.internal.protolog.ProtoLogViewerConfigReader;
-import com.android.internal.protolog.common.IProtoLogGroup;
-
-import java.io.File;
-import java.io.PrintWriter;
-
-
-/**
- * A service for the ProtoLog logging system.
- */
-public class ShellProtoLogImpl extends BaseProtoLogImpl {
- private static final String TAG = "ProtoLogImpl";
- private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: find a proper location to save the protolog message file
- private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
- private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
-
- private static ShellProtoLogImpl sServiceInstance = null;
-
- static {
- addLogGroupEnum(ShellProtoLogGroup.values());
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
- args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
- public static boolean isEnabled(IProtoLogGroup group) {
- return group.isLogToLogcat()
- || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
- }
-
- /**
- * Returns the single instance of the ProtoLogImpl singleton class.
- */
- public static synchronized ShellProtoLogImpl getSingleInstance() {
- if (sServiceInstance == null) {
- sServiceInstance = new ShellProtoLogImpl();
- }
- return sServiceInstance;
- }
-
- public int startTextLogging(String[] groups, PrintWriter pw) {
- mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- }
-
- public int stopTextLogging(String[] groups, PrintWriter pw) {
- return setLogging(true /* setTextLogging */, false, pw, groups);
- }
-
- private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
- new ProtoLogViewerConfigReader());
- }
-}
-
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 e4213569b526..1c54754e9953 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
@@ -163,14 +163,14 @@ 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, SplitBounds splitBounds) {
+ public boolean addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
if (taskId1 == taskId2) {
- return;
+ return false;
}
if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2
&& mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) {
// If the two tasks are already paired and the bounds are the same, then skip updating
- return;
+ return false;
}
// Remove any previous pairs
removeSplitPair(taskId1);
@@ -185,6 +185,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
notifyRecentTasksChanged();
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
taskId1, taskId2, splitBounds);
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea6d19d..b5ea1b1b43ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -19,10 +19,12 @@ package com.android.wm.shell.recents;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
@@ -58,10 +60,10 @@ import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -591,7 +593,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
cancel("transit_sleep");
return;
}
- if (mKeyguardLocked || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: keyguard is locked", mInstanceId);
// We will not accept new changes if we are swiping over the keyguard.
@@ -928,7 +930,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
Slog.e(TAG, "Duplicate call to finish");
return;
}
- if (!toHome) {
+
+ boolean returningToApp = !toHome
+ && !mWillFinishToHome
+ && mPausingTasks != null
+ && mState == STATE_NORMAL;
+ if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+ } else if (!toHome) {
// For some transitions, we may have notified home activity that it became visible.
// We need to notify the observer that we are no longer going home.
mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
@@ -947,7 +956,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ if (returningToApp) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
@@ -1020,7 +1029,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsController.finishInner: no valid PiP leash;"
+ "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
- mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ mPipTransaction, mPipTask, mPipTaskId);
} else {
t.show(pipLeash);
PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
@@ -1047,6 +1056,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
+ if (tasks == null || tasks.isEmpty()) {
+ return false;
+ }
+ for (int i = tasks.size() - 1; i >= 0; --i) {
+ if (!tasks.get(i).mIsTranslucent) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
if (!sendUserLeaveHint && task.isLeaf()) {
@@ -1117,6 +1138,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
/** The surface/leash of the task provided by Core. */
SurfaceControl mTaskSurface;
+ /** True when the task is translucent. */
+ final boolean mIsTranslucent;
+
/** The (local) animation-leash created for this task. Only non-null for leafs. */
@Nullable
SurfaceControl mLeash;
@@ -1125,6 +1149,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mToken = change.getContainer();
mTaskInfo = change.getTaskInfo();
mTaskSurface = change.getLeash();
+ mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
mLeash = leash;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 253acc49071a..0ca244c4b96a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -158,5 +158,10 @@ interface ISplitScreen {
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+ /**
+ * Reverse the split.
+ */
+ oneway void switchSplitPosition() = 22;
}
-// Last id = 21 \ No newline at end of file
+// Last id = 22 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index e52235fda80f..64e26dbd70be 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,11 +16,14 @@
package com.android.wm.shell.splitscreen;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -50,6 +53,8 @@ class MainStage extends StageTaskListener {
void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: main stage includingTopTask=%b",
+ includingTopTask);
if (includingTopTask) {
reparentTopTask(wct);
@@ -64,6 +69,8 @@ class MainStage extends StageTaskListener {
void deactivate(WindowContainerTransaction wct, boolean toTop) {
if (!mIsActive) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: main stage toTop=%b rootTaskInfo=%s",
+ toTop, mRootTaskInfo);
mIsActive = false;
if (mRootTaskInfo == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
index 7237d2bde39f..37ccd15587e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
@@ -1,2 +1,4 @@
# WM shell sub-modules splitscreen owner
chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
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 9903113c5453..f5fbae55960a 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,12 +16,15 @@
package com.android.wm.shell.splitscreen;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
import android.app.ActivityManager;
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -47,6 +50,8 @@ class SideStage extends StageTaskListener {
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
+ mChildrenTaskInfo.size(), toTop);
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
@@ -59,6 +64,8 @@ class SideStage extends StageTaskListener {
boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId,
+ task != null);
if (task == null) return false;
wct.reparent(task.token, newParent, false /* onTop */);
return true;
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 7b5709769369..952e2d4b3b9a 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
@@ -25,20 +25,20 @@ 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.MultiInstanceHelper.getComponent;
+import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
+import static com.android.wm.shell.common.MultiInstanceHelper.samePackage;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
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.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
-import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -47,6 +47,7 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -68,6 +69,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -81,6 +84,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -133,7 +137,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
public static final int EXIT_REASON_RECREATE_SPLIT = 10;
public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
- public static final int EXIT_REASON_ENTER_DESKTOP = 12;
+ public static final int EXIT_REASON_DESKTOP_MODE = 12;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -147,7 +151,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
EXIT_REASON_RECREATE_SPLIT,
EXIT_REASON_FULLSCREEN_SHORTCUT,
- EXIT_REASON_ENTER_DESKTOP
+ EXIT_REASON_DESKTOP_MODE
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -171,13 +175,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
+ private final LauncherApps mLauncherApps;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
@@ -185,8 +190,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final MultiInstanceHelper mMultiInstanceHelpher;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
- private final String[] mAppsSupportMultiInstances;
@VisibleForTesting
StageCoordinator mStageCoordinator;
@@ -196,6 +201,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private SurfaceControl mGoingToRecentsTasksLayer;
private SurfaceControl mStartingSplitTasksLayer;
+ /**
+ * @param stageCoordinator if null, a stage coordinator will be created when this controller is
+ * initialized. Can be non-null for testing purposes.
+ */
public SplitScreenController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
@@ -206,7 +215,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -214,12 +223,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
+ @Nullable StageCoordinator stageCoordinator,
+ MultiInstanceHelper multiInstanceHelper,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -233,63 +245,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
mDesktopTasksController = desktopTasksController;
+ mStageCoordinator = stageCoordinator;
+ mMultiInstanceHelpher = multiInstanceHelper;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
-
- // TODO(255224696): Remove the config once having a way for client apps to opt-in
- // multi-instances split.
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
- }
-
- @VisibleForTesting
- SplitScreenController(Context context,
- ShellInit shellInit,
- ShellCommandHandler shellCommandHandler,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- DisplayController displayController,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- DragAndDropController dragAndDropController,
- Transitions transitions,
- TransactionPool transactionPool,
- IconProvider iconProvider,
- RecentTasksController recentTasks,
- LaunchAdjacentController launchAdjacentController,
- WindowDecorViewModel windowDecorViewModel,
- DesktopTasksController desktopTasksController,
- ShellExecutor mainExecutor,
- StageCoordinator stageCoordinator) {
- mShellCommandHandler = shellCommandHandler;
- mShellController = shellController;
- mTaskOrganizer = shellTaskOrganizer;
- mSyncQueue = syncQueue;
- mContext = context;
- mRootTDAOrganizer = rootTDAOrganizer;
- mMainExecutor = mainExecutor;
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = Optional.of(dragAndDropController);
- mTransitions = transitions;
- mTransactionPool = transactionPool;
- mIconProvider = iconProvider;
- mRecentTasksOptional = Optional.of(recentTasks);
- mLaunchAdjacentController = launchAdjacentController;
- mWindowDecorViewModel = Optional.of(windowDecorViewModel);
- mDesktopTasksController = Optional.of(desktopTasksController);
- mStageCoordinator = stageCoordinator;
- mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
- shellInit.addInitCallback(this::onInit, this);
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -316,7 +279,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
}
- mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+ if (mDragAndDropController != null) {
+ mDragAndDropController.setSplitScreenController(this);
+ }
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
@@ -440,6 +405,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
/**
+ * Performs previous child eviction and such to prepare for the pip task expending into one of
+ * the split stages
+ *
+ * @param taskInfo TaskInfo of the pip task
+ */
+ public void onPipExpandToSplit(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ mStageCoordinator.onPipExpandToSplit(wct, taskInfo);
+ }
+
+ /**
* Doing necessary window transaction for other transition handler need to exit split in
* transition.
*/
@@ -500,12 +476,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void goToFullscreenFromSplit() {
- mStageCoordinator.goToFullscreenFromSplit();
+ if (mStageCoordinator.isSplitActive()) {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
}
/** Move the specified task to fullscreen, regardless of focus state. */
- public void moveTaskToFullscreen(int taskId) {
- mStageCoordinator.moveTaskToFullscreen(taskId);
+ public void moveTaskToFullscreen(int taskId, int exitReason) {
+ mStageCoordinator.moveTaskToFullscreen(taskId, exitReason);
}
public boolean isLaunchToSplit(TaskInfo taskInfo) {
@@ -588,7 +566,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
- if (supportMultiInstancesSplit(packageName)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
+ getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else if (isSplitScreenVisible()) {
@@ -609,7 +588,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
activityOptions.toBundle(), user);
}
- void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +600,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -640,7 +619,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
- void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
@@ -653,7 +632,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -692,7 +671,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +701,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -757,7 +736,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
@@ -794,7 +773,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
setSecondIntentMultipleTask = true;
@@ -829,6 +808,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
+ fillInIntent, position);
// Flag this as a no-user-action launch to prevent sending user leaving event to the current
// top activity since it's going to be put into another side of the split. This prevents the
// current top activity from going into pip mode due to user leaving event.
@@ -847,6 +829,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
.map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
.orElse(null);
if (taskInfo != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
mStageCoordinator.startTask(taskInfo.taskId, position, options);
} else {
@@ -856,7 +840,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) {
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,19 +899,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return taskInfo != null ? taskInfo.userId : -1;
}
- @VisibleForTesting
- boolean supportMultiInstancesSplit(String packageName) {
- if (packageName != null) {
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
/**
* Determines whether the widgetIntent needs to be modified if multiple tasks of its
* corresponding package/app are supported. There are 4 possible paths:
@@ -1038,6 +1009,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onDroppedToSplit(position, dragSessionId);
}
+ void switchSplitPosition(String reason) {
+ if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition(reason);
+ }
+ }
+
/**
* Return the {@param exitReason} as a string.
*/
@@ -1065,8 +1042,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "CHILD_TASK_ENTER_PIP";
case EXIT_REASON_RECREATE_SPLIT:
return "RECREATE_SPLIT";
- case EXIT_REASON_ENTER_DESKTOP:
- return "ENTER_DESKTOP";
+ case EXIT_REASON_DESKTOP_MODE:
+ return "DESKTOP_MODE";
default:
return "unknown reason, reason int = " + exitReason;
}
@@ -1402,5 +1379,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
true /* blocking */);
return out[0];
}
+
+ @Override
+ public void switchSplitPosition() {
+ executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+ (controller) -> controller.switchSplitPosition("remoteCall"));
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9a306b..7f16c5e3592e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@ public class SplitScreenShellCommandHandler implements
return runRemoveFromSideStage(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
+ case "switchSplitPosition":
+ return runSwitchSplitPosition();
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -84,6 +86,11 @@ public class SplitScreenShellCommandHandler implements
return true;
}
+ private boolean runSwitchSplitPosition() {
+ mController.switchSplitPosition("shellCommand");
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@ public class SplitScreenShellCommandHandler implements
pw.println(prefix + " Remove a task with given id in split-screen mode.");
pw.println(prefix + "setSideStagePosition <SideStagePosition>");
pw.println(prefix + " Sets the position of the side-stage.");
+ pw.println(prefix + "switchSplitPosition");
+ pw.println(prefix + " Reverses the split.");
}
}
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 2c0ba92524ad..1a53a1d10dd2 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
@@ -25,6 +25,8 @@ 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.common.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -48,9 +50,9 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -101,6 +103,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playAnimation: transition=%d", info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
final TransitSession pendingTransition = getPendingTransition(transition);
@@ -123,10 +126,12 @@ class SplitScreenTransitions {
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
- /** Internal funcation of playAnimation. */
+ /** Internal function of playAnimation. */
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: transition=%d",
+ info.getDebugId());
// Play some place-holder fade animations
final boolean isEnter = isPendingEnter(transition);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -220,6 +225,8 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
@NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playDragDismissAnimation: transition=%d",
+ info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -259,6 +266,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -312,13 +320,15 @@ class SplitScreenTransitions {
@Nullable
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved enter transition");
return mPendingEnter;
} else if (isPendingDismiss(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved dismiss transition");
return mPendingDismiss;
} else if (isPendingResize(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition");
return mPendingResize;
}
-
return null;
}
@@ -339,7 +349,7 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler,
int extraTransitType, boolean resizeAnim) {
if (mPendingEnter != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
return null;
}
@@ -355,8 +365,10 @@ class SplitScreenTransitions {
mPendingEnter = new EnterSession(
transition, remoteTransition, extraTransitType, resizeAnim);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setEnterTransition: transitType=%d resize=%b",
+ extraTransitType, resizeAnim);
}
/** Starts a transition to dismiss split. */
@@ -364,7 +376,7 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
if (mPendingDismiss != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start dismiss split transition since it already exist. reason to "
+ " dismiss = %s", exitReasonToString(reason));
return null;
@@ -381,32 +393,35 @@ class SplitScreenTransitions {
@SplitScreenController.ExitReason int reason) {
mPendingDismiss = new DismissSession(transition, reason, dismissTop);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
exitReasonToString(reason), stageTypeToString(dismissTop));
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDismissTransition: reason=%s dismissTop=%s",
+ exitReasonToString(reason), stageTypeToString(dismissTop));
}
IBinder startResizeTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler,
- @Nullable TransitionFinishedCallback finishCallback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishCallback,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " splitTransition deduced Resize split screen.");
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
+ mPendingResize != null);
if (mPendingResize != null) {
+ mainDecor.cancelRunningAnimations();
+ sideDecor.cancelRunningAnimations();
mPendingResize.cancel(null);
mAnimations.clear();
onFinish(null /* wct */);
}
IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
- setResizeTransition(transition, finishCallback);
+ mPendingResize = new TransitSession(transition, consumedCallback, finishCallback);
return transition;
}
- void setResizeTransition(@NonNull IBinder transition,
- @Nullable TransitionFinishedCallback finishCallback) {
- mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Resize split screen");
- }
-
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
@@ -442,13 +457,18 @@ class SplitScreenTransitions {
mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
} else if (isPendingDismiss(transition)) {
mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for dismiss transition");
} else if (isPendingResize(transition)) {
mPendingResize.onConsumed(aborted);
mPendingResize = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition");
}
+
+ // TODO: handle transition consumed for active remote handler
}
void onFinish(WindowContainerTransaction wct) {
@@ -458,12 +478,15 @@ class SplitScreenTransitions {
if (isPendingEnter(mAnimatingTransition)) {
mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for enter transition");
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for dismiss transition");
} else if (isPendingResize(mAnimatingTransition)) {
mPendingResize.onFinished(wct, mFinishTransaction);
mPendingResize = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition");
}
mActiveRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index f4ab2266179a..a0bf843444df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -25,7 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
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__FULLSCREEN_SHORTCUT;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
@@ -43,7 +43,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -194,8 +194,8 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
case EXIT_REASON_FULLSCREEN_SHORTCUT:
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
- case EXIT_REASON_ENTER_DESKTOP:
- return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
+ case EXIT_REASON_DESKTOP_MODE:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
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 96e57e71f05c..76504447339f 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
@@ -44,6 +44,9 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -52,6 +55,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
@@ -64,8 +68,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
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;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -135,13 +137,14 @@ import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
-import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
@@ -313,6 +316,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task");
mMainStage = new MainStage(
mContext,
mTaskOrganizer,
@@ -454,6 +458,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId,
+ stagePosition);
prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
if (ENABLE_SHELL_TRANSITIONS) {
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
@@ -474,6 +480,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
boolean removeFromSideStage(int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId);
final WindowContainerTransaction wct = new WindowContainerTransaction();
/**
@@ -498,11 +505,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
taskBounds);
}
- if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+ if (enteredSplitSelect) {
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
Bundle options, UserHandle user) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: pkg=%s id=%s position=%d user=%d",
+ packageName, shortcutId, position, user.getIdentifier());
final boolean isEnteringSplit = !isSplitActive();
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -564,6 +575,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Use this method to launch an existing Task via a taskId */
void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
@@ -595,6 +607,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
+ position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
@@ -690,6 +704,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startTasks: task1=%d task2=%d position=%d snapPosition=%d",
+ taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
@@ -718,6 +735,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
+ pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -740,6 +760,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startShortcutAndTask: shortcut=%s task1=%d position=%d snapPosition=%d",
+ shortcutInfo, taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -801,6 +824,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d",
+ pendingIntent1.getIntent(), pendingIntent2.getIntent(), splitPosition,
+ snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
options1 = options1 != null ? options1 : new Bundle();
@@ -1302,6 +1329,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void switchSplitPosition(String reason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition");
final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
@@ -1343,7 +1371,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
});
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLeftRightSplit());
@@ -1376,11 +1404,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!mMainStage.isActive()) {
return;
}
-
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
setDividerVisibility(!mKeyguardShowing, null);
}
void onFinishedWakingUp() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
if (!mMainStage.isActive()) {
return;
}
@@ -1421,6 +1450,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
+ toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
if (!mMainStage.isActive()) return;
StageTaskListener childrenToTop = null;
@@ -1439,6 +1470,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
+ childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive());
if (!mMainStage.isActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1447,6 +1480,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s",
+ exitReasonToString(exitReason));
if (!mMainStage.isActive() || mIsExiting) return;
onSplitScreenExit();
@@ -1502,7 +1537,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
- Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
// Log the exit
if (childrenToTop != null) {
logExitToStage(exitReason, childrenToTop == mMainStage);
@@ -1527,6 +1561,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitStage: stageToClose=%d", stageToClose);
mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
}
@@ -1540,12 +1575,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
try {
activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
} catch (RemoteException | NullPointerException e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.e(WM_SHELL_SPLIT_SCREEN,
"Unable to update focus on the chosen stage: %s", e.getMessage());
}
}
private void clearRequestIfPresented() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
&& mMainStageListener.mVisible && mSideStageListener.mHasChildren) {
mSplitRequest = null;
@@ -1572,6 +1608,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// The device is folded
case EXIT_REASON_FULLSCREEN_SHORTCUT:
// User has used a keyboard shortcut to go back to fullscreen from split
+ case EXIT_REASON_DESKTOP_MODE:
+ // One of the children enters desktop mode
return true;
default:
return false;
@@ -1581,6 +1619,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void clearSplitPairedInRecents(@ExitReason int exitReason) {
if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s",
+ exitReasonToString(exitReason));
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
@@ -1597,11 +1637,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop);
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen");
prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
!mIsDropEntering);
}
@@ -1613,6 +1655,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareEnterSplitScreen(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b",
+ startPosition, resizeAnim);
onSplitScreenEnter();
// Preemptively reset the reparenting behavior if we know that we are entering, as starting
// split tasks with activity trampolines can inadvertently trigger the task to be
@@ -1629,6 +1673,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void prepareBringSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareBringSplit: task=%d isSplitVisible=%b",
+ taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1649,6 +1695,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void prepareActiveSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b",
+ taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
if (!ENABLE_SHELL_TRANSITIONS) {
// Legacy transition we need to create divider here, shell transition case we will
// create it on #finishEnterSplitScreen
@@ -1667,6 +1715,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareSplitLayout: resize=%b", resizeAnim);
if (resizeAnim) {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
} else {
@@ -1686,6 +1735,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
mSplitLayout.update(finishT, true /* resetImePosition */);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
@@ -1835,12 +1885,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
- recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
+ boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
+ splitBounds);
+ if (added) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "updateRecentTasksSplitPair: adding split pair ltTask=%d rbTask=%d",
+ leftTopTaskId, rightBottomTaskId);
+ }
}
});
}
private void sendSplitVisibilityChanged() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "sendSplitVisibilityChanged: dividerVisible=%b",
+ mDividerVisible);
for (int i = mListeners.size() - 1; i >= 0; --i) {
final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible);
@@ -1855,6 +1913,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
}
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%s", taskInfo);
mRootTaskInfo = taskInfo;
mRootTaskLeash = leash;
@@ -1880,6 +1939,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating",
+ taskInfo.taskId);
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config. Don't reset the IME state since those updates are not in
// sync with task info changes.
@@ -1892,6 +1953,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%s", taskInfo);
if (mRootTaskInfo == null) {
throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo);
}
@@ -1911,6 +1973,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onRootTaskAppeared() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
+ mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask);
// Wait unit all root tasks appeared.
if (mRootTaskInfo == null
|| !mMainStageListener.mHasRootTask
@@ -1937,6 +2001,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* #onStageHasChildrenChanged because this would be called every time child task appeared.
* NOTICE: This only be called on legacy transition. */
private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d",
+ stageListener == mMainStageListener, taskId);
// Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
&& mSplitRequest == null) {
@@ -1960,6 +2026,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void onRootTaskVanished() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished");
final WindowContainerTransaction wct = new WindowContainerTransaction();
mLaunchAdjacentController.clearLaunchAdjacentRoot();
applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
@@ -1990,6 +2057,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
+ // TODO Protolog
+
// Check if it needs to dismiss split screen when both stage invisible.
if (!mainStageVisible && mExitSplitScreenOnHide) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -2020,14 +2089,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Request to %s divider bar from %s.",
- (visible ? "show" : "hide"), Debug.getCaller());
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
+ visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
if (visible && mKeyguardShowing) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Defer showing divider bar due to keyguard showing.");
return;
}
@@ -2036,7 +2105,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sendSplitVisibilityChanged();
if (mIsDividerRemoteAnimating) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
}
@@ -2050,12 +2119,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to divider leash not ready.");
return;
}
if (mIsDividerRemoteAnimating) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
}
@@ -2119,6 +2188,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Callback when split roots have child or haven't under it.
* NOTICE: This only be called on legacy transition. */
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
+ stageListener == mMainStageListener);
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
@@ -2170,13 +2241,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
+ public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
+ bottomOrRight, exitReasonToString(exitReason));
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
if (!ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(toTopStage, reason);
+ exitSplitScreen(toTopStage, exitReason);
return;
}
@@ -2219,6 +2292,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onLayoutSizeChanged");
// Reset this flag every time onLayoutSizeChanged.
mShowDecorImmediately = false;
@@ -2236,8 +2310,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sendOnBoundsChanged();
if (ENABLE_SHELL_TRANSITIONS) {
mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
- mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) ->
- mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"));
+ mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
+ }, (finishWct, t) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+ }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
} else {
// Only need screenshot for legacy case because shell transition should screenshot
// itself during transition.
@@ -2275,8 +2352,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
+ boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
bottomRightStage.mRootTaskInfo);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s",
+ layout.getBounds1(), layout.getBounds2());
+ return updated;
}
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
@@ -2288,6 +2368,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
applyResizingOffset);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s",
+ layout.getBounds1(), layout.getBounds2());
}
@Override
@@ -2326,6 +2409,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLayoutOffsetTarget: x=%d y=%d",
+ offsetX, offsetY);
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
@@ -2340,6 +2425,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (displayId != DEFAULT_DISPLAY) {
return;
}
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayAdded: display=%d", displayId);
mDisplayController.addDisplayChangingController(this::onDisplayChange);
}
@@ -2354,8 +2440,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
- if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return;
+ if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) {
+ return;
+ }
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onDisplayChange: display=%d fromRot=%d toRot=%d config=%s",
+ displayId, fromRotation, toRotation,
+ newDisplayAreaInfo != null ? newDisplayAreaInfo.configuration : null);
mSplitLayout.rotateTo(toRotation);
if (newDisplayAreaInfo != null) {
mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
@@ -2366,6 +2458,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onFoldedStateChanged(boolean folded) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
@@ -2436,6 +2529,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
if (isSplitActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
+ request.getDebugId());
// Check if the display is rotating.
final TransitionRequestInfo.DisplayChange displayChange =
request.getDisplayChange();
@@ -2464,6 +2559,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (isSplitActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active",
+ request.getDebugId());
// 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"
@@ -2538,6 +2635,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return null;
} else {
if (isOpening && getStageOfTask(triggerTask) != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
+ request.getDebugId());
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
@@ -2554,6 +2653,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request,
@NonNull WindowContainerTransaction outWCT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addEnterOrExitIfNeeded: transition=%d",
+ request.getDebugId());
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask != null && triggerTask.displayId != mDisplayId) {
// Skip handling task on the other display.
@@ -2588,6 +2689,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
@@ -2599,6 +2701,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed");
mSplitTransitions.onTransitionConsumed(transition, aborted, finishT);
}
@@ -2614,6 +2717,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If we're not in split-mode, just abort so something else can handle it.
if (!mMainStage.isActive()) return false;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId());
mSplitLayout.setFreezeDividerWindow(false);
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
@@ -2724,6 +2828,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
startTransaction, finishTransaction, finishCallback)) {
if (mSplitTransitions.isPendingResize(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startAnimation: transition=%d display change", info.getDebugId());
// Only need to update in resize because divider exist before transition.
mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
@@ -2794,6 +2900,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d",
+ info.getDebugId());
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(transition,
@@ -2827,6 +2935,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Called to clean-up state and do house-keeping after the animation is done. */
public void onTransitionAnimationComplete() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete");
// If still playing, let it finish.
if (!mMainStage.isActive() && !mIsExiting) {
// Update divider state after animation so that it is still around and positioned
@@ -2839,6 +2948,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingEnterAnimation: enterTransition=%s",
+ enterTransition);
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
@@ -2956,17 +3067,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
public void goToFullscreenFromSplit() {
- boolean leftOrTop;
- if (mSideStage.isFocused()) {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "goToFullscreenFromSplit");
+ // If main stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false
+ // If side stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_TOP_OR_LEFT. Otherwise toEnd = false
+ final boolean toEnd;
+ if (mMainStage.isFocused()) {
+ toEnd = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
} else {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ toEnd = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
- mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_FULLSCREEN_SHORTCUT);
}
/** Move the specified task to fullscreen, regardless of focus state. */
- public void moveTaskToFullscreen(int taskId) {
+ public void moveTaskToFullscreen(int taskId, int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveTaskToFullscreen");
boolean leftOrTop;
if (mMainStage.containsTask(taskId)) {
leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
@@ -2975,8 +3092,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else {
return;
}
- mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, exitReason);
+
+ }
+
+ /**
+ * Performs previous child eviction and such to prepare for the pip task expending into one of
+ * the split stages
+ *
+ * @param taskInfo TaskInfo of the pip task
+ */
+ public void onPipExpandToSplit(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo);
+ prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo),
+ false /*resizeAnim*/);
+
+ if (!isSplitScreenVisible() || mSplitRequest == null) {
+ return;
+ }
+ boolean replacingMainStage = getMainStagePosition() == mSplitRequest.mActivatePosition;
+ (replacingMainStage ? mMainStage : mSideStage).evictOtherChildren(wct, taskInfo.taskId);
}
boolean isLaunchToSplit(TaskInfo taskInfo) {
@@ -3014,6 +3151,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "prepareDismissAnimation: transition=%d toStage=%d reason=%s",
+ info.getDebugId(), toStage, exitReasonToString(dismissReason));
// 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
@@ -3100,6 +3240,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SplitScreenTransitions.DismissSession dismissTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startPendingDismissAnimation: transition=%d dismissTransition=%s",
+ info.getDebugId(), dismissTransition);
prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
t, finishT);
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
@@ -3120,6 +3263,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when starting the open-recents animation while split-screen is active. */
public void onRecentsInSplitAnimationStart(TransitionInfo info) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationStart: transition=%d",
+ info.getDebugId());
if (isSplitScreenVisible()) {
// Cache tasks on live tile.
for (int i = 0; i < info.getChanges().size(); ++i) {
@@ -3152,6 +3297,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation during split-screen finishes. */
public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish");
mPausingTasks.clear();
// Check if the recent transition is finished by returning to the current
// split, so we can restore the divider bar.
@@ -3177,6 +3323,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation finishes by doing pair-to-pair switch. */
public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish");
// Pair-to-pair switch happened so here should evict the live tile from its stage.
// Otherwise, the task will remain in stage, and occluding the new task when next time
// user entering recents.
@@ -3258,6 +3405,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* handled.
*/
private void setSplitsVisible(boolean visible) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
}
@@ -3266,6 +3414,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Sets drag info to be logged when splitscreen is next entered.
*/
public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDroppedToSplit: position=%d", position);
if (!isSplitScreenVisible()) {
mIsDropEntering = true;
mSkipEvictingMainStageChildren = true;
@@ -3282,7 +3431,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* Sets info to be logged when splitscreen is next entered.
*/
- public void onRequestToSplit(InstanceId sessionId, int enterReason) {
+ public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason);
if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
// If split running background, exit split first.
// Skip this on shell transition due to we could evict existing tasks on transition
@@ -3358,6 +3508,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3367,15 +3518,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ // If visible, we preserve the app and keep it running. If an app becomes
+ // unsupported in the bg, break split without putting anything on top
+ boolean splitScreenVisible = isSplitScreenVisible();
+ int stageType = STAGE_TYPE_UNDEFINED;
+ if (splitScreenVisible) {
+ stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
+ clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
"app package " + taskInfo.baseActivity.getPackageName()
+ " does not support splitscreen, or is a controlled activity type"));
- mSplitUnsupportedToast.show();
+ if (splitScreenVisible) {
+ mSplitUnsupportedToast.show();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index af7bf360f036..f33ab33dafcc 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
@@ -25,6 +25,7 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
@@ -44,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -175,6 +177,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+ taskInfo.taskId, taskInfo.parentTaskId,
+ mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
@@ -225,6 +230,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|| !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
|| !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
taskInfo.getWindowingMode())) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onTaskInfoChanged: task=%d no longer supports multiwindow",
+ taskInfo.taskId);
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
@@ -251,6 +259,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId);
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
@@ -333,6 +342,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addTask: task=%d", task.taskId);
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
@@ -342,6 +352,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b", taskId, onTop);
if (!containsTask(taskId)) {
return;
}
@@ -357,6 +368,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
/** Collects all the current child tasks and prepares transaction to evict them to display. */
void evictAllChildren(WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children");
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
@@ -367,11 +379,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (taskId == taskInfo.taskId) continue;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren");
final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
for (int i = 0; i < apps.length; i++) {
if (apps[i].mode == MODE_OPENING) {
@@ -380,6 +394,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
for (int i = toBeEvict.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
@@ -388,12 +403,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (!taskInfo.isVisible) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d",
+ taskInfo.taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
}
void evictChildren(WindowContainerTransaction wct, int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId);
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId);
if (taskInfo != null) {
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index c10142588bde..e330f3ab65ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -74,20 +75,20 @@ public class TvSplitScreenController extends SplitScreenController {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ MultiInstanceHelper multiInstanceHelper,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
+ displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- Optional.empty(), mainExecutor);
+ Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index e86b62dee86d..6325c686a682 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,7 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - roundedCornerRadius;
+ mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index ae21c4bf5450..da2965c05ee4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -73,6 +73,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -245,16 +246,19 @@ public class SplashscreenContentDrawer {
} else {
windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
}
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
+ ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ : params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
final int displayId = taskInfo.displayId;
// Assumes it's safe to show starting windows of launched apps while
// the keyguard is being hidden. This is okay because starting windows never show
@@ -277,11 +281,6 @@ public class SplashscreenContentDrawer {
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
params.setTitle("Splash Screen " + title);
return params;
}
@@ -418,7 +417,7 @@ public class SplashscreenContentDrawer {
final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
final SplashScreenView view = builder
.setWindowBGColor(themeBGColor)
- .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+ .chooseStyle(STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN)
.build();
view.setNotCopyable();
return view;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index e6418f35a0b1..1a0c011205fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -135,7 +135,6 @@ public class TaskSnapshotWindow {
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
}
- window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
@@ -161,7 +160,7 @@ public class TaskSnapshotWindow {
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
- mWindow = new Window();
+ mWindow = new Window(this);
mWindow.setSession(mSession);
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
@@ -204,9 +203,9 @@ public class TaskSnapshotWindow {
}
static class Window extends BaseIWindow {
- private WeakReference<TaskSnapshotWindow> mOuter;
+ private final WeakReference<TaskSnapshotWindow> mOuter;
- public void setOuter(TaskSnapshotWindow outer) {
+ Window(TaskSnapshotWindow outer) {
mOuter = new WeakReference<>(outer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 0eb7c2d98e0a..a7843e218a8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -293,11 +293,7 @@ public class ShellController {
private class ShellInterfaceImpl implements ShellInterface {
@Override
public void onInit() {
- try {
- mMainExecutor.executeBlocking(() -> ShellController.this.handleInit());
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize the Shell in 2s", e);
- }
+ mMainExecutor.execute(ShellController.this::handleInit);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 93d763608b5f..196e04edbb10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -480,7 +480,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 34c015f05c68..198ec82b5f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,8 +37,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Objects;
@@ -330,7 +330,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
continue;
}
if (isHide) {
- if (pending.mType == TRANSIT_TO_BACK) {
+ if (pending != null && pending.mType == TRANSIT_TO_BACK) {
// TO_BACK is only used when setting the task view visibility immediately,
// so in that case we can also hide the surface immediately
startTransaction.hide(chg.getLeash());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 628ce27fe514..b03daaafd70c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -27,8 +27,8 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 9f20f49b4094..8746b8c8d55c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -22,17 +22,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-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.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,12 +48,11 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
@@ -84,7 +75,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private UnfoldTransitionHandler mUnfoldHandler;
private ActivityEmbeddingController mActivityEmbeddingController;
- private class MixedTransition {
+ abstract static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -124,6 +115,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
+ protected final Transitions mPlayer;
+ protected final DefaultMixedHandler mMixedHandler;
+ protected final PipTransitionController mPipHandler;
+ protected final StageCoordinator mSplitHandler;
+ protected final KeyguardTransitionHandler mKeyguardHandler;
+
Transitions.TransitionHandler mLeftoversHandler = null;
TransitionInfo mInfo = null;
WindowContainerTransaction mFinishWCT = null;
@@ -144,12 +141,35 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
*/
int mInFlightSubAnimations = 0;
- MixedTransition(int type, IBinder transition) {
+ MixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
mType = type;
mTransition = transition;
- }
-
- boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+ mPlayer = player;
+ mMixedHandler = mixedHandler;
+ mPipHandler = pipHandler;
+ mSplitHandler = splitHandler;
+ mKeyguardHandler = keyguardHandler;
+ }
+
+ abstract boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+ abstract void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+ abstract void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT);
+
+ protected boolean startSubAnimation(
+ Transitions.TransitionHandler handler, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -164,7 +184,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return true;
}
- void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+ private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
mInFlightSubAnimations--;
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -175,7 +195,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
joinFinishArgs(wct);
if (mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(MixedTransition.this);
mFinishCB.onTransitionFinished(mFinishWCT);
}
}
@@ -236,8 +255,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
throw new IllegalStateException("Unexpected remote transition in"
+ "pip-enter-from-split request");
}
- mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
- transition));
+ mActiveTransitions.add(createDefaultMixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
WindowContainerTransaction out = new WindowContainerTransaction();
mPipHandler.augmentRequest(transition, request, out);
@@ -248,7 +267,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mActivityEmbeddingController != null)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" Got a PiP-enter request from an Activity Embedding split");
- mActiveTransitions.add(new MixedTransition(
+ mActiveTransitions.add(createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
// Postpone transition splitting to later.
WindowContainerTransaction out = new WindowContainerTransaction();
@@ -267,7 +286,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (handler == null) {
return null;
}
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -293,7 +312,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mPlayer.getRemoteTransitionHandler(),
new WindowContainerTransaction());
}
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -302,16 +321,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
final WindowContainerTransaction wct =
mUnfoldHandler.handleRequest(transition, request);
if (wct != null) {
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_UNFOLD, transition);
- mixed.mLeftoversHandler = mUnfoldHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createDefaultMixedTransition(
+ MixedTransition.TYPE_UNFOLD, transition));
}
return wct;
}
return null;
}
+ private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
+ return new DefaultMixedTransition(
+ type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
+ mUnfoldHandler, mActivityEmbeddingController);
+ }
+
@Override
public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
if (mRecentsHandler != null) {
@@ -331,31 +354,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private void setRecentsTransitionDuringSplit(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "Split-Screen is foreground, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
}
private void setRecentsTransitionDuringKeyguard(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "keyguard is visible, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
}
private void setRecentsTransitionDuringDesktop(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "desktop mode is active, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
}
- private TransitionInfo subCopy(@NonNull TransitionInfo info,
+ private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
+ return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
+ mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
+ }
+
+ static TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
out.setTrack(info.getTrack());
@@ -372,15 +394,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
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,
@@ -399,10 +412,15 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (KeyguardTransitionHandler.handles(info)) {
if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
final MixedTransition keyguardMixed =
- new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
mActiveTransitions.add(keyguardMixed);
- final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
- startTransaction, finishTransaction, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(keyguardMixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ final boolean hasAnimateKeyguard = animateKeyguard(
+ keyguardMixed, info, startTransaction, finishTransaction, callback,
+ mKeyguardHandler, mPipHandler);
if (hasAnimateKeyguard) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
@@ -420,277 +438,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed == null) return false;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- return false;
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
- startTransaction, finishTransaction, finishCallback);
- // Consume the transition on remote handler if the leftover handler already handle this
- // transition. And if it cannot, the transition will be handled by remote handler, so
- // don't consume here.
- // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
- if (handledToPip && mixed.mHasRequestToRemote
- && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
- }
- return handledToPip;
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // Pip auto-entering info might be appended to recent transition like pressing
- // home-key in 3-button navigation. This offers split handler the opportunity to
- // handle split to pip animation.
- if (mPipHandler.isEnteringPip(change, info.getType())
- && mSplitHandler.getSplitItemPosition(change.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- return animateEnterPipFromSplit(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- }
- }
-
- return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- return animateKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
- } else {
- mActiveTransitions.remove(mixed);
- throw new IllegalStateException("Starting mixed animation without a known mixed type? "
- + mixed.mType);
- }
- }
-
- private boolean animateEnterPipFromActivityEmbedding(@NonNull 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 from an Activity Embedding window");
- // Split into two transitions (wct)
- TransitionInfo.Change pipChange = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- 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);
- }
- }
-
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
- };
-
- if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
- // Fallback to dispatching to other handlers.
- return false;
- }
-
- // PIP window should always be on the highest Z order.
- if (pipChange != null) {
- mixed.mInFlightSubAnimations = 2;
- mPipHandler.startEnterAnimation(
- pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
- finishTransaction,
- finishCB);
- } else {
- mixed.mInFlightSubAnimations = 1;
- }
-
- mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
- startTransaction, finishTransaction, finishCB);
- return true;
- }
-
- private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- TransitionInfo.Change pipChange = null;
- 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;
- info.getChanges().remove(i);
- }
- }
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
- };
- if (pipChange == null) {
- if (mixed.mLeftoversHandler != null) {
- mixed.mInFlightSubAnimations = 1;
- if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
- info, startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- }
- mActiveTransitions.remove(mixed);
- return false;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
- + " animation because remote-animation likely doesn't support it");
- // Split the transition into 2 parts: the pip part and the rest.
- mixed.mInFlightSubAnimations = 2;
- // 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();
-
- mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
-
- // Dispatch the rest of the transition normally.
- if (mixed.mLeftoversHandler != null
- && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB, this);
- return true;
- }
-
- 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 foreground.");
- TransitionInfo.Change pipChange = null;
- TransitionInfo.Change wallpaper = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- 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.
- mActiveTransitions.remove(mixed);
- return false;
- }
- final boolean isGoingHome = homeIsOpening;
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- if (isGoingHome) {
- mSplitHandler.onTransitionAnimationComplete();
- }
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ final MixedTransition chosenTransition = mixed;
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(chosenTransition);
+ finishCallback.onTransitionFinished(wct);
};
- if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- 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();
- @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
- if (mSplitHandler.isSplitScreenVisible()) {
- // The non-going home case, we could be pip-ing one of the split stages and keep
- // showing the other
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange) {
- // Ignore the change/task that's going into Pip
- continue;
- }
- @SplitScreen.StageType int splitItemStage =
- mSplitHandler.getSplitItemStage(change.getLastParent());
- if (splitItemStage != STAGE_TYPE_UNDEFINED) {
- topStageToKeep = splitItemStage;
- break;
- }
- }
- }
- // Let split update internal state for dismiss.
- mSplitHandler.prepareDismissAnimation(topStageToKeep,
- 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.setEnterAnimationType(ANIM_TYPE_ALPHA);
- mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
- finishCB);
- // Dispatch the rest of the transition normally. This will most-likely be taken by
- // recents or default handler.
- mixed.mLeftoversHandler = 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);
+ boolean handled = chosenTransition.startAnimation(
+ transition, info, startTransaction, finishTransaction, callback);
+ if (!handled) {
+ mActiveTransitions.remove(chosenTransition);
}
- return true;
+ return handled;
}
private void unlinkMissingParents(TransitionInfo from) {
@@ -724,10 +483,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
Transitions.TransitionFinishCallback finishCallback) {
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
mActiveTransitions.add(mixed);
- return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ return mixed.startAnimation(transition, info, startT, finishT, callback);
}
/**
@@ -750,7 +513,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
}
if (displayPart.getChanges().isEmpty()) return false;
unlinkMissingParents(everythingElse);
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
mActiveTransitions.add(mixed);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
@@ -779,116 +542,22 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return true;
}
- private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ private static boolean animateKeyguard(@NonNull final MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Split-screen is only interested in the recents transition finishing (and merging), so
- // just wrap finish and start recents animation directly.
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mixed.mInFlightSubAnimations = 0;
- mActiveTransitions.remove(mixed);
- // If pair-to-pair switching, the post-recents clean-up isn't needed.
- wct = wct != null ? wct : new WindowContainerTransaction();
- if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
- mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
- } else {
- // notify pair-to-pair recents animation finish
- mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
- }
- mSplitHandler.onTransitionAnimationComplete();
- finishCallback.onTransitionFinished(wct);
- };
- mixed.mInFlightSubAnimations = 1;
- mSplitHandler.onRecentsInSplitAnimationStart(info);
- final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB);
- if (!handled) {
- mSplitHandler.onRecentsInSplitAnimationCanceled();
- mActiveTransitions.remove(mixed);
- }
- return handled;
- }
-
- private boolean animateKeyguard(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull KeyguardTransitionHandler keyguardHandler,
+ PipTransitionController pipHandler) {
if (mixed.mFinishT == null) {
mixed.mFinishT = finishTransaction;
mixed.mFinishCB = finishCallback;
}
// Sync pip state.
- if (mPipHandler != null) {
- mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ if (pipHandler != null) {
+ pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
- return mixed.startSubAnimation(mKeyguardHandler, info, startTransaction, finishTransaction);
- }
-
- private boolean animateRecentsDuringKeyguard(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mixed.mInfo == null) {
- mixed.mInfo = info;
- mixed.mFinishT = finishTransaction;
- mixed.mFinishCB = finishCallback;
- }
- return mixed.startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
- }
-
- private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- Transitions.TransitionFinishCallback finishCB = wct -> {
- mixed.mInFlightSubAnimations--;
- if (mixed.mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct);
- }
- };
-
- mixed.mInFlightSubAnimations++;
- boolean consumed = mRecentsHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
- if (!consumed) {
- mixed.mInFlightSubAnimations--;
- return false;
- }
- if (mDesktopTasksController != null) {
- mDesktopTasksController.syncSurfaceState(info, finishTransaction);
- return true;
- }
-
- return false;
- }
-
- private boolean animateUnfold(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mixed.mInFlightSubAnimations--;
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct);
- };
- mixed.mInFlightSubAnimations = 1;
- // Sync pip state.
- if (mPipHandler != null) {
- mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
- }
- if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
- mSplitHandler.updateSurfaces(startTransaction);
- }
- return mUnfoldHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+ return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
}
/** Use to when split use intent to enter, check if this enter transition should be mixed or
@@ -932,65 +601,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
+
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
return;
}
- if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- // queue since no actual animation.
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
- boolean ended = mSplitHandler.end();
- // If split couldn't end (because it is remote), then don't end everything else
- // since we have to play out the animation anyways.
- if (!ended) return;
- mPipHandler.end();
- if (mixed.mLeftoversHandler != null) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- }
- } else {
- mPipHandler.end();
- }
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.end();
- mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mPipHandler.end();
- if (mixed.mLeftoversHandler != null) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- }
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- if (mSplitHandler.isPendingEnter(transition)) {
- // Recents -> enter-split means that we are switching from one pair to
- // another pair.
- mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
- }
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- handoverTransitionLeashes(mixed, info, t, mixed.mFinishT);
- if (animateKeyguard(mixed, info, t, mixed.mFinishT, mixed.mFinishCB)) {
- finishCallback.onTransitionFinished(null);
- }
- }
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else {
- throw new IllegalStateException("Playing a mixed transition with unknown type? "
- + mixed.mType);
- }
+ mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
}
@@ -1003,46 +620,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mixed = mActiveTransitions.remove(i);
break;
}
- if (mixed == null) return;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
- }
- if (mixed.mHasRequestToRemote) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ if (mixed != null) {
+ mixed.onTransitionConsumed(transition, aborted, finishT);
}
}
/**
- * Update an incoming {@link TransitionInfo} with the leashes from an ongoing
- * {@link MixedTransition} so that it can take over some parts of the animation without
+ * Update an incoming {@link TransitionInfo} with the leashes from an existing
+ * {@link TransitionInfo} so that it can take over some parts of the animation without
* reparenting to new transition roots.
*/
- private static void handoverTransitionLeashes(@NonNull MixedTransition mixed,
- @NonNull TransitionInfo info,
+ static void handoverTransitionLeashes(
+ @NonNull TransitionInfo from,
+ @NonNull TransitionInfo to,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
// Show the roots in case they contain new changes not present in the original transition.
- for (int j = info.getRootCount() - 1; j >= 0; --j) {
- startT.show(info.getRoot(j).getLeash());
+ for (int j = to.getRootCount() - 1; j >= 0; --j) {
+ startT.show(to.getRoot(j).getLeash());
}
// Find all of the leashes from the original transition.
Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>();
- for (TransitionInfo.Change oldChange : mixed.mInfo.getChanges()) {
+ for (TransitionInfo.Change oldChange : from.getChanges()) {
if (oldChange.getContainer() != null) {
originalChanges.put(oldChange.getContainer(), oldChange);
}
@@ -1050,9 +651,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// Merge the animation leashes by re-using the original ones if we see the same container
// in the new transition and the old.
- for (TransitionInfo.Change newChange : info.getChanges()) {
+ for (TransitionInfo.Change newChange : to.getChanges()) {
if (originalChanges.containsKey(newChange.getContainer())) {
- final TransitionInfo.Change oldChange = originalChanges.get(newChange.getContainer());
+ final TransitionInfo.Change oldChange = originalChanges.get(
+ newChange.getContainer());
startT.reparent(newChange.getLeash(), null);
newChange.setLeash(oldChange.getLeash());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
new file mode 100644
index 000000000000..e9cd73b0df5e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final UnfoldTransitionHandler mUnfoldHandler;
+ private final ActivityEmbeddingController mActivityEmbeddingController;
+
+ DefaultMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ UnfoldTransitionHandler unfoldHandler,
+ ActivityEmbeddingController activityEmbeddingController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mUnfoldHandler = unfoldHandler;
+ mActivityEmbeddingController = activityEmbeddingController;
+
+ switch (type) {
+ case TYPE_UNFOLD:
+ mLeftoversHandler = mUnfoldHandler;
+ break;
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ case TYPE_KEYGUARD:
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ default:
+ break;
+ }
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
+ animateEnterPipFromActivityEmbedding(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_ENTER_PIP_FROM_SPLIT ->
+ animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ case TYPE_KEYGUARD ->
+ animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
+ mKeyguardHandler, mPipHandler);
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE ->
+ animateOpenIntentWithRemoteAndPip(transition, info, startTransaction,
+ finishTransaction, finishCallback);
+ case TYPE_UNFOLD ->
+ animateUnfold(info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting default mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateEnterPipFromActivityEmbedding(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for entering PIP from"
+ + " an Activity Embedding window #%d", info.getDebugId());
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ 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);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(
+ mTransition, everythingElse, startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
+ private boolean animateOpenIntentWithRemoteAndPip(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent"
+ + " with a remote transition and PIP #%d", info.getDebugId());
+ boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
+ info, startTransaction, finishTransaction, finishCallback);
+ // Consume the transition on remote handler if the leftover handler already handle this
+ // transition. And if it cannot, the transition will be handled by remote handler, so don't
+ // consume here.
+ // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+ if (handledToPip && mHasRequestToRemote
+ && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+ }
+ return handledToPip;
+ }
+
+ private boolean tryAnimateOpenIntentWithRemoteAndPip(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = null;
+ 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;
+ info.getChanges().remove(i);
+ }
+ }
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+ if (pipChange == null) {
+ if (mLeftoversHandler != null) {
+ mInFlightSubAnimations = 1;
+ if (mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate"
+ + " animation because remote-animation likely doesn't support it #%d",
+ info.getDebugId());
+ // Split the transition into 2 parts: the pip part and the rest.
+ mInFlightSubAnimations = 2;
+ // 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();
+
+ mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+ // Dispatch the rest of the transition normally.
+ if (mLeftoversHandler != null
+ && mLeftoversHandler.startAnimation(mTransition, info,
+ startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ mLeftoversHandler = mPlayer.dispatchTransition(
+ mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler);
+ return true;
+ }
+
+ private boolean animateUnfold(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for unfolding #%d",
+ info.getDebugId());
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ // Sync pip state.
+ if (mPipHandler != null) {
+ mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+ mSplitHandler.updateSurfaces(startTransaction);
+ }
+ return mUnfoldHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ // queue since no actual animation.
+ return;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ if (mAnimType == ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ return;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ return;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a default mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 193a4fb5b503..9130edfa9f26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,8 +109,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -470,10 +470,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
final float cornerRadius;
- if (a.hasRoundedCorners() && isTask) {
- // hasRoundedCorners is currently only enabled for tasks
+ if (a.hasRoundedCorners()) {
+ final int displayId = isTask ? change.getTaskInfo().displayId
+ : info.getRoot(TransitionUtil.rootIndexFor(change, info))
+ .getDisplayId();
final Context displayContext =
- mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
+ mDisplayController.getDisplayContext(displayId);
cornerRadius = displayContext == null ? 0
: ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba3b7d2..cb2944c120e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -31,11 +32,12 @@ import android.window.TransitionInfo;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@ public class HomeTransitionObserver implements TransitionObserver,
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c68da27..72fba3bb7de4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@ import android.window.RemoteTransition;
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
new file mode 100644
index 000000000000..0974cd13f249
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.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.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+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.transition.DefaultMixedHandler.subCopy;
+
+import android.annotation.NonNull;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+public class MixedTransitionHelper {
+ static boolean animateEnterPipFromSplit(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP while Split-Screen is foreground.");
+ TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change wallpaper = null;
+ final TransitionInfo everythingElse =
+ subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ boolean homeIsOpening = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (pipHandler.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;
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ if (isGoingHome) {
+ splitHandler.onTransitionAnimationComplete();
+ }
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+ if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ 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();
+ @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+ if (splitHandler.isSplitScreenVisible()) {
+ // The non-going home case, we could be pip-ing one of the split stages and keep
+ // showing the other
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange) {
+ // Ignore the change/task that's going into Pip
+ continue;
+ }
+ @SplitScreen.StageType int splitItemStage =
+ splitHandler.getSplitItemStage(change.getLastParent());
+ if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+ topStageToKeep = splitItemStage;
+ break;
+ }
+ }
+ }
+ // Let split update internal state for dismiss.
+ splitHandler.prepareDismissAnimation(topStageToKeep,
+ 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;
+ }
+ }
+
+ pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+ pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ // Dispatch the rest of the transition normally. This will most-likely be taken by
+ // recents or default handler.
+ mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, mixedHandler);
+ } 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;
+ pipHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+ return true;
+ }
+
+ private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+ return change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+ }
+
+ private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+ }
+
+ static boolean animateKeyguard(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull KeyguardTransitionHandler keyguardHandler,
+ PipTransitionController pipHandler) {
+ if (mixed.mFinishT == null) {
+ mixed.mFinishT = finishTransaction;
+ mixed.mFinishCB = finishCallback;
+ }
+ // Sync pip state.
+ if (pipHandler != null) {
+ pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4355ed2bd3bf..94519a0d118c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -74,6 +74,9 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
@@ -82,8 +85,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
}
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
+ mRemote = null;
});
- mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -115,17 +118,24 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished merging one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
// We have merged, since we sent the transaction over binder, the one in this
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
t.clear();
- mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
- mRemote = null;
+ mMainExecutor.execute(() -> {
+ finishCallback.onTransitionFinished(wct);
+ mRemote = null;
+ });
}
};
try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
new file mode 100644
index 000000000000..4ea71490798c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final RecentsTransitionHandler mRecentsHandler;
+ private final DesktopTasksController mDesktopTasksController;
+
+ RecentsMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ RecentsTransitionHandler recentsHandler,
+ DesktopTasksController desktopTasksController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mRecentsHandler = recentsHandler;
+ mDesktopTasksController = desktopTasksController;
+ mLeftoversHandler = mRecentsHandler;
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP ->
+ animateRecentsDuringDesktop(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_KEYGUARD ->
+ animateRecentsDuringKeyguard(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_SPLIT ->
+ animateRecentsDuringSplit(
+ info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting Recents mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateRecentsDuringDesktop(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition for Recents during"
+ + " Desktop #%d", info.getDebugId());
+
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ Transitions.TransitionFinishCallback finishCB = wct -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations == 0) {
+ finishCallback.onTransitionFinished(wct);
+ }
+ };
+
+ mInFlightSubAnimations++;
+ boolean consumed = mRecentsHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!consumed) {
+ mInFlightSubAnimations--;
+ return false;
+ }
+ if (mDesktopTasksController != null) {
+ mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean animateRecentsDuringKeyguard(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " Keyguard #%d", info.getDebugId());
+
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
+ }
+
+ private boolean animateRecentsDuringSplit(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " split screen #%d", info.getDebugId());
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // Pip auto-entering info might be appended to recent transition like pressing
+ // home-key in 3-button navigation. This offers split handler the opportunity to
+ // handle split to pip animation.
+ if (mPipHandler.isEnteringPip(change, info.getType())
+ && mSplitHandler.getSplitItemPosition(change.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ }
+ }
+
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations = 0;
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ } else {
+ // notify pair-to-pair recents animation finish
+ mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(info);
+ final boolean handled = mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mSplitHandler.onRecentsInSplitAnimationCanceled();
+ }
+ return handled;
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_KEYGUARD:
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ handoverTransitionLeashes(mInfo, info, t, mFinishT);
+ if (animateKeyguard(
+ this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+ finishCallback.onTransitionFinished(null);
+ }
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_SPLIT:
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ case TYPE_RECENTS_DURING_SPLIT:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 293b66084d28..4c4c5806ea55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,7 +39,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b012d359931a..1be85d05c16e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -56,7 +56,7 @@ import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
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 af69b5272ad5..b8a0f6703b97 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
@@ -37,9 +37,9 @@ import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -80,10 +80,13 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -172,6 +175,9 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition to animate task to desktop. */
public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+ /** Transition to resize PiP task. */
+ public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -184,7 +190,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
- private final Tracer mTracer = new Tracer();
+ private final TransitionTracer mTransitionTracer;
private boolean mIsRegistered = false;
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +313,12 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
mHomeTransitionObserver = observer;
+
+ if (android.tracing.Flags.perfettoTransitionTracing()) {
+ mTransitionTracer = new PerfettoTransitionTracer();
+ } else {
+ mTransitionTracer = new LegacyTransitionTracer();
+ }
}
private void onInit() {
@@ -481,11 +493,9 @@ public class Transitions implements RemoteCallable<Transitions>,
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
- if (mode == TRANSIT_TO_FRONT
- && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
- || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
- // When the window is moved to front with a different size, make sure the crop is
- // updated to prevent it from using the old crop.
+ if (mode == TRANSIT_TO_FRONT) {
+ // When the window is moved to front, make sure the crop is updated to prevent it
+ // from using the old crop.
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
}
@@ -757,6 +767,10 @@ public class Transitions implements RemoteCallable<Transitions>,
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
+ } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+ // Remove the change because it should be invisible in the animation.
+ info.getChanges().remove(i);
+ continue;
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
@@ -864,7 +878,7 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
- mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct) -> onMerged(playing, ready));
}
@@ -898,7 +912,7 @@ public class Transitions implements RemoteCallable<Transitions>,
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
- mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
// See if we should merge another transition.
processReadyQueue(track);
}
@@ -919,7 +933,7 @@ public class Transitions implements RemoteCallable<Transitions>,
active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
- mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+ mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
return;
}
}
@@ -944,7 +958,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+ mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
return mHandlers.get(i);
}
}
@@ -974,7 +988,7 @@ public class Transitions implements RemoteCallable<Transitions>,
final Track track = mTracks.get(transition.getTrack());
transition.mAborted = true;
- mTracer.logAborted(transition.mInfo.getDebugId());
+ mTransitionTracer.logAborted(transition.mInfo.getDebugId());
if (transition.mHandler != null) {
// Notifies to clean-up the aborted transition.
@@ -1154,7 +1168,11 @@ public class Transitions implements RemoteCallable<Transitions>,
mPendingTransitions.add(0, active);
}
- /** Start a new transition directly. */
+ /**
+ * Start a new transition directly.
+ * @param handler if null, the transition will be dispatched to the registered set of transition
+ * handlers to be handled
+ */
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
@@ -1502,12 +1520,18 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
-
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
switch (args[0]) {
case "tracing": {
- mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ ((LegacyTransitionTracer) mTransitionTracer)
+ .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ } else {
+ pw.println("Command not supported. Use the Perfetto command instead to start "
+ + "and stop this trace instead.");
+ return false;
+ }
return true;
}
default: {
@@ -1520,8 +1544,10 @@ public class Transitions implements RemoteCallable<Transitions>,
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
- pw.println(prefix + "tracing");
- mTracer.printShellCommandHelp(pw, prefix + " ");
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ pw.println(prefix + "tracing");
+ ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + " ");
+ }
}
private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
index 5919aad133c7..9c848869e0f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.transition.tracing;
import static android.os.Build.IS_USER;
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.internal.util.TraceBuffer;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
import com.google.protobuf.nano.MessageNano;
@@ -45,7 +46,8 @@ import java.util.concurrent.TimeUnit;
/**
* Helper class to collect and dump transition traces.
*/
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
+public class LegacyTransitionTracer
+ implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
@@ -60,33 +62,33 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
private final TraceBuffer.ProtoProvider mProtoProvider =
new TraceBuffer.ProtoProvider<MessageNano,
- com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition>() {
- @Override
- public int getItemSize(MessageNano proto) {
- return proto.getCachedSize();
- }
-
- @Override
- public byte[] getBytes(MessageNano proto) {
- return MessageNano.toByteArray(proto);
- }
-
- @Override
- public void write(
- com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
- Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition>() {
+ @Override
+ public int getItemSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ @Override
+ public byte[] getBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public void write(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+ Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
throws IOException {
- encapsulatingProto.transitions = buffer.toArray(
- new com.android.wm.shell.nano.Transition[0]);
- os.write(getBytes(encapsulatingProto));
- }
- };
+ encapsulatingProto.transitions = buffer.toArray(
+ new com.android.wm.shell.nano.Transition[0]);
+ os.write(getBytes(encapsulatingProto));
+ }
+ };
private final TraceBuffer<MessageNano,
com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition> mTraceBuffer
- = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
- (proto) -> handleOnEntryRemovedFromTrace(proto));
+ com.android.wm.shell.nano.Transition> mTraceBuffer =
+ new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+ this::handleOnEntryRemovedFromTrace);
private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -99,6 +101,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param transitionId The id of the transition being dispatched.
* @param handler The handler the transition is being dispatched to.
*/
+ @Override
public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
final int handlerId;
if (mHandlerIds.containsKey(handler)) {
@@ -130,6 +133,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
*/
+ @Override
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
@@ -145,6 +149,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param mergedTransitionId The id of the transition that was merged.
* @param playingTransitionId The id of the transition the transition was merged into.
*/
+ @Override
public void logMerged(int mergedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
@@ -159,6 +164,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param transitionId The id of the transition that was aborted.
*/
+ @Override
public void logAborted(int transitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = transitionId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 000000000000..2ff4d90306a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
+natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 000000000000..fa331af267fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+ private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+ private final TransitionDataSource mDataSource = new TransitionDataSource(
+ mActiveTraces::incrementAndGet,
+ this::onFlush,
+ mActiveTraces::decrementAndGet);
+ private final Map<String, Integer> mHandlerMapping = new HashMap<>();
+
+ public PerfettoTransitionTracer() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ @Override
+ public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ mDataSource.trace(ctx -> {
+ final int handlerId = getHandlerId(handler);
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId);
+ os.end(token);
+ });
+ }
+
+ private int getHandlerId(Transitions.TransitionHandler handler) {
+ final int handlerId;
+ synchronized (mHandlerMapping) {
+ if (mHandlerMapping.containsKey(handler.getClass().getName())) {
+ handlerId = mHandlerMapping.get(handler.getClass().getName());
+ } else {
+ // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+ handlerId = mHandlerMapping.size() + 1;
+ mHandlerMapping.put(handler.getClass().getName(), handlerId);
+ }
+ }
+ return handlerId;
+ }
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ @Override
+ public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ @Override
+ public void logMerged(int mergedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ @Override
+ public void logAborted(int transitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ });
+ }
+
+ private boolean isTracing() {
+ return mActiveTraces.get() > 0;
+ }
+
+ private void onFlush() {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+ for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
+ final String handler = entry.getKey();
+ final int handlerId = entry.getValue();
+
+ final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING);
+ os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
+ os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
+ os.end(mappingEntryToken);
+
+ }
+ os.end(mappingsToken);
+
+ ctx.flush();
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 000000000000..5857ad88e9e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ void logMerged(int mergedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index d7cb490ed0cb..e6d35e83116b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -28,11 +28,11 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import dagger.Lazy;
+
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-
/**
* Manages fold/unfold animations of tasks on foldable devices.
* When folding or unfolding a foldable device we play animations that
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 98d343b66760..c26604a84a61 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
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -43,7 +44,6 @@ 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 com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 9b48a542720c..7a50814f0275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.util
import android.util.Log
import com.android.internal.protolog.common.IProtoLogGroup
-import com.android.wm.shell.protolog.ShellProtoLogImpl
+import com.android.internal.protolog.common.ProtoLog
/**
* Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
@@ -31,42 +31,42 @@ class KtProtoLog {
companion object {
/** @see [com.android.internal.protolog.common.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.d(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.v(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.i(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.w(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.e(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.wtf(group.tag, String.format(messageString, *args))
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cf1692018518..b2eeea7048bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,13 +23,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -54,6 +52,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
+ private final Transitions mTransitions;
private TaskOperations mTaskOperations;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -64,29 +63,21 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ Transitions transitions) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ mTransitions = transitions;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
}
@Override
- public void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- @Override
- public void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- @Override
- public void onTransitionFinished(IBinder transition) {}
-
- @Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
@@ -133,7 +124,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
}
@@ -145,7 +137,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
@Override
@@ -191,16 +184,17 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- final DragPositioningCallback dragPositioningCallback =
- new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- 0 /* disallowedAreaForEndBoundsHeight */);
+ final FluidResizeTaskPositioner taskPositioner =
+ new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
+ mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
final CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragPositioningCallback(dragPositioningCallback);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
+ windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
setupCaptionColor(taskInfo, windowDecoration);
}
@@ -277,6 +271,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b3772e..91e9601c6a27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WindowingMode;
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;
@@ -34,6 +35,7 @@ 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.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
@@ -92,15 +157,21 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
+ boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -118,6 +189,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+ mRelayoutParams.mAllowCaptionInputFallthrough = false;
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -214,6 +287,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
closeBackground.setTintList(buttonTintColor);
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index ab29df1f780c..f4ccd689f938 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,18 +21,26 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -43,11 +51,14 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
@@ -58,13 +69,9 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -74,14 +81,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
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.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -91,6 +98,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import java.io.PrintWriter;
import java.util.Optional;
@@ -105,6 +113,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "DesktopModeWindowDecorViewModel";
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
+ private final IWindowManager mWindowManager;
+ private final ShellExecutor mMainExecutor;
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -114,8 +124,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopTasksController> mDesktopTasksController;
- private final RecentsTransitionHandler mRecentsTransitionHandler;
+ private final DesktopTasksController mDesktopTasksController;
+ private final InputManager mInputManager;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -123,8 +133,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final ExclusionRegionListener mExclusionRegionListener =
new ExclusionRegionListenerImpl();
- private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
- new SparseArray<>();
+ private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
@@ -139,14 +148,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final DisplayInsetsController mDisplayInsetsController;
+ private final Region mExclusionRegion = Region.obtain();
private boolean mInImmersiveMode;
+ private final ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ mMainExecutor.execute(() -> {
+ mExclusionRegion.set(systemGestureExclusion);
+ });
+ }
+ };
+
public DesktopModeWindowDecorViewModel(
Context context,
+ ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -154,15 +180,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
context,
+ shellExecutor,
mainHandler,
mainChoreographer,
shellInit,
shellCommandHandler,
+ windowManager,
taskOrganizer,
displayController,
shellController,
@@ -170,20 +197,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ new SparseArray<>());
}
@VisibleForTesting
DesktopModeWindowDecorViewModel(
Context context,
+ ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -191,12 +220,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
mContext = context;
+ mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
@@ -206,28 +236,32 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopTasksController = desktopTasksController;
- mRecentsTransitionHandler = recentsTransitionHandler;
+ mDesktopTasksController = desktopTasksController.get();
mShellCommandHandler = shellCommandHandler;
+ mWindowManager = windowManager;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mInputManager = mContext.getSystemService(InputManager.class);
+ mWindowDecorByTaskId = windowDecorByTaskId;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
- mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
- @Override
- public void onTransitionStarted(IBinder transition) {
- blockRelayoutOnTransitionStarted(transition);
- }
- });
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
+ mDesktopTasksController.setOnTaskResizeAnimationListener(
+ new DeskopModeOnTaskResizeAnimationListener());
+ try {
+ mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
+ mContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register window manager callbacks", e);
+ }
}
@Override
@@ -245,7 +279,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
}
@@ -264,48 +298,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
- public void onTransitionReady(
- @NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change) {
- if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
- || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
- .addTransitionPausingRelayout(transition);
- } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
- && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- && change.getTaskInfo() != null) {
- final DesktopModeWindowDecoration decor =
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
- if (decor != null) {
- decor.addTransitionPausingRelayout(transition);
- }
- } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
- && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
- && change.getTaskInfo() != null) {
- blockRelayoutOnTransitionStarted(transition);
- }
- }
-
- @Override
- public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.mergeTransitionPausingRelayout(merged, playing);
- }
- }
-
- @Override
- public void onTransitionFinished(@NonNull IBinder transition) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.removeTransitionPausingRelayout(transition);
- }
- }
-
- @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
@@ -335,7 +327,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
}
@@ -347,13 +340,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
@Override
public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
- final DesktopModeWindowDecoration decoration =
- mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
decoration.close();
@@ -361,21 +354,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
- }
-
- private void blockRelayoutOnTransitionStarted(IBinder transition) {
- // Block relayout on window decorations originating from #onTaskInfoChanges until the
- // animation completes to avoid interfering with the transition animation.
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.incrementRelayoutBlock();
- decor.addTransitionPausingRelayout(transition);
- }
+ // Remove the decoration from the cache last because WindowDecoration#close could still
+ // issue CANCEL MotionEvents to touch listeners before its view host is released.
+ // See b/327664694.
+ mWindowDecorByTaskId.remove(taskInfo.taskId);
}
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
- DragDetector.MotionEventHandler {
+ View.OnGenericMotionListener , DragDetector.MotionEventHandler {
+ private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -383,10 +371,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragDetector mDragDetector;
private final GestureDetector mGestureDetector;
+ /**
+ * Whether to pilfer the next motion event to send cancellations to the windows below.
+ * Useful when the caption window is spy and the gesture should be handle by the system
+ * instead of by the app for their custom header content.
+ */
+ private boolean mShouldPilferCaptionEvents;
private boolean mIsDragging;
+ private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
- private boolean mShouldClick;
private int mDragPointerId = -1;
+ private final Runnable mCloseMaximizeWindowRunnable;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
@@ -396,15 +391,25 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
mGestureDetector = new GestureDetector(mContext, this);
+ mCloseMaximizeWindowRunnable = () -> {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ if (decoration == null) return;
+ decoration.closeMaximizeMenu();
+ };
}
@Override
public void onClick(View v) {
+ if (mIsDragging) {
+ mIsDragging = false;
+ return;
+ }
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId);
+ mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
mTaskOperations.closeTask(mTaskToken);
}
@@ -412,68 +417,55 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskOperations.injectBackKey();
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
if (!decoration.isHandleMenuActive()) {
- moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+ moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu();
} else {
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- if (mDesktopTasksController.isPresent()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- decoration.incrementRelayoutBlock();
- mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
- closeOtherSplitTask(mTaskId);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+ mDesktopTasksController.moveToDesktop(mTaskId, wct);
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
decoration.closeHandleMenu();
if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(mTaskId);
+ mSplitScreenController.moveTaskToFullscreen(mTaskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
- mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+ mDesktopTasksController.moveToFullscreen(mTaskId);
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
- mDesktopTasksController.ifPresent(c -> {
- c.requestSplit(decoration.mTaskInfo);
- });
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
- mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
+ mDesktopTasksController.moveToNextDisplay(mTaskId);
}
} else if (id == R.id.maximize_window) {
- if (decoration.isMaximizeMenuActive()) {
- decoration.closeMaximizeMenu();
- return;
- }
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, decoration));
decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
} else if (id == R.id.maximize_menu_maximize_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_left_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_right_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -482,39 +474,68 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
+ if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
+ mTouchscreenInUse = e.getActionMasked() != ACTION_UP
+ && e.getActionMasked() != ACTION_CANCEL;
+ }
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
&& id != R.id.open_menu_button && id != R.id.close_window
&& id != R.id.maximize_window) {
return false;
}
- moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
-
- if (!mHasLongClicked) {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- decoration.closeMaximizeMenu();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ moveTaskToFront(decoration.mTaskInfo);
+
+ final int actionMasked = e.getActionMasked();
+ final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+ final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
+ || actionMasked == MotionEvent.ACTION_UP;
+ if (isDown) {
+ final boolean downInCustomizableCaptionRegion =
+ decoration.checkTouchEventInCustomizableRegion(e);
+ final boolean downInExclusionRegion = mExclusionRegion.contains(
+ (int) e.getRawX(), (int) e.getRawY());
+ final boolean isTransparentCaption =
+ TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
+ // The caption window may be a spy window when the caption background is
+ // transparent, which means events will fall through to the app window. Make
+ // sure to cancel these events if they do not happen in the intersection of the
+ // customizable region and what the app reported as exclusion areas, because
+ // the drag-move or other caption gestures should take priority outside those
+ // regions.
+ mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption);
}
- final long eventDuration = e.getEventTime() - e.getDownTime();
- final boolean shouldLongClick = id == R.id.maximize_window && !mIsDragging
- && !mHasLongClicked && eventDuration >= ViewConfiguration.getLongPressTimeout();
- if (shouldLongClick) {
- v.performLongClick();
- mHasLongClicked = true;
- return true;
+ if (!mShouldPilferCaptionEvents) {
+ // The event will be handled by a window below.
+ return false;
+ }
+ // Otherwise pilfer so that windows below receive cancellations for this gesture, and
+ // continue normal handling as a caption gesture.
+ if (mInputManager != null) {
+ mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ }
+ if (isUpOrCancel) {
+ // Gesture is finished, reset state.
+ mShouldPilferCaptionEvents = false;
+ }
+ if (!mHasLongClicked && id != R.id.maximize_window) {
+ decoration.closeMaximizeMenuIfNeeded(e);
}
-
return mDragDetector.onMotionEvent(v, e);
}
@Override
public boolean onLongClick(View v) {
final int id = v.getId();
- if (id == R.id.maximize_window) {
+ if (id == R.id.maximize_window && mTouchscreenInUse) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
moveTaskToFront(decoration.mTaskInfo);
if (decoration.isMaximizeMenuActive()) {
decoration.closeMaximizeMenu();
} else {
+ mHasLongClicked = true;
decoration.createMaximizeMenu();
}
return true;
@@ -522,9 +543,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent ev) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final int id = v.getId();
+ if (ev.getAction() == ACTION_HOVER_ENTER) {
+ if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
+ decoration.onMaximizeWindowHoverEnter();
+ } else if (id == R.id.maximize_window
+ || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
+ // Re-hovering over any of the maximize menu views should keep the menu open by
+ // cancelling any attempts to close the menu.
+ mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
+ }
+ return true;
+ } else if (ev.getAction() == ACTION_HOVER_EXIT) {
+ if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
+ decoration.onMaximizeWindowHoverExit();
+ } else if (id == R.id.maximize_window || id == R.id.maximize_menu) {
+ // Close menu if not hovering over maximize menu or maximize button after a
+ // delay to give user a chance to re-enter view or to move from one maximize
+ // menu view to another.
+ mMainHandler.postDelayed(mCloseMaximizeWindowRunnable,
+ CLOSE_MAXIMIZE_MENU_DELAY_MS);
+ }
+ return true;
+ }
+ return false;
+ }
+
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
- mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+ mDesktopTasksController.moveTaskToFront(taskInfo);
}
}
@@ -534,7 +584,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
*/
@Override
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
if (DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
@@ -542,11 +593,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
- if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
- // performed.
- mShouldClick = false;
- }
+ final int id = v.getId();
+ final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
+ || id == R.id.open_menu_button);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -554,13 +603,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
- mShouldClick = true;
mHasLongClicked = false;
- return true;
+ // Do not consume input event if a button is touched, otherwise it would
+ // prevent the button's ripple effect from showing.
+ return !touchingButton;
}
case MotionEvent.ACTION_MOVE: {
- final DesktopModeWindowDecoration decoration =
- mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -568,23 +618,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ mDesktopTasksController.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
- new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds));
+ e.getRawX(dragPointerIdx),
+ newTaskBounds);
mIsDragging = true;
- mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final boolean wasDragging = mIsDragging;
if (!wasDragging) {
- if (mShouldClick && v != null && !mHasLongClicked) {
- v.performClick();
- mShouldClick = false;
- return true;
- }
return false;
}
if (e.findPointerIndex(mDragPointerId) == -1) {
@@ -599,23 +643,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position,
+ mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
- mIsDragging = false;
- return true;
+ newTaskBounds);
+ if (touchingButton && !mHasLongClicked) {
+ // We need the input event to not be consumed here to end the ripple
+ // effect on the touched button. We will reset drag state in the ensuing
+ // onClick call that results.
+ return false;
+ } else {
+ mIsDragging = false;
+ return true;
+ }
}
}
return true;
}
+ /**
+ * Perform a task size toggle on release of the double-tap, assuming no drag event
+ * was handled during the double-tap.
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event should be consumed, false if not
+ */
@Override
- public boolean onDoubleTap(@NonNull MotionEvent e) {
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- mDesktopTasksController.ifPresent(c -> {
- c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
- });
+ public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+ final int action = e.getActionMasked();
+ if (mIsDragging || (action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL)) {
+ return false;
+ }
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
return true;
}
}
@@ -711,7 +770,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
- handleEventOutsideFocusedCaption(ev, relevantDecor);
+ handleEventOutsideCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
@@ -720,8 +779,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
- private void handleEventOutsideFocusedCaption(MotionEvent ev,
+ /**
+ * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+ * maximize the menu.
+ *
+ * @param relevantDecor the window decoration of the focused task's caption. This method only
+ * handles motion events outside this caption's bounds.
+ */
+ private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
// Returns if event occurs within caption
if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
@@ -759,7 +824,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
- if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
+ if (dragFromStatusBarAllowed
+ && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
mTransitionDragActive = true;
}
}
@@ -772,20 +838,29 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return;
}
if (mTransitionDragActive) {
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
mTransitionDragActive = false;
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > 2 * statusBarHeight) {
+ if (indicatorType == TO_DESKTOP_INDICATOR
+ || indicatorType == TO_SPLIT_LEFT_INDICATOR
+ || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
}
mMoveToDesktopAnimator = null;
return;
} else if (mMoveToDesktopAnimator != null) {
- mDesktopTasksController.ifPresent(
- c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
mMoveToDesktopAnimator = null;
return;
+ } else {
+ // In cases where we create an indicator but do not start the
+ // move-to-desktop animation, we need to dismiss it.
+ mDesktopTasksController.releaseVisualIndicator();
}
}
relevantDecor.checkClickEvent(ev);
@@ -797,32 +872,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return;
}
if (mTransitionDragActive) {
- mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningMoveThroughStatusBar(
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getY()));
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > statusBarHeight) {
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+ if (indicatorType != TO_FULLSCREEN_INDICATOR) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
- mDesktopTasksController.ifPresent(
- c -> {
- final int taskId = relevantDecor.mTaskInfo.taskId;
- relevantDecor.incrementRelayoutBlock();
- if (isTaskInSplitScreen(taskId)) {
- final DesktopModeWindowDecoration otherDecor =
- mWindowDecorByTaskId.get(
- getOtherSplitTask(taskId).taskId);
- if (otherDecor != null) {
- otherDecor.incrementRelayoutBlock();
- }
- }
- c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor);
- });
+ mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator);
}
}
if (mMoveToDesktopAnimator != null) {
@@ -844,16 +904,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* @param scale the amount to scale to relative to the Screen Bounds
*/
private Rect calculateFreeformBounds(int displayId, float scale) {
+ // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
final int screenWidth = displayLayout.width();
final int screenHeight = displayLayout.height();
final float adjustmentPercentage = (1f - scale) / 2;
- final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage),
+ return new Rect((int) (screenWidth * adjustmentPercentage),
(int) (screenHeight * adjustmentPercentage),
(int) (screenWidth * (adjustmentPercentage + scale)),
(int) (screenHeight * (adjustmentPercentage + scale)));
- return endBounds;
}
/**
@@ -868,6 +928,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
* @param relevantDecor the window decor of the task to be animated
* @param ev the motion event that triggers the animation
+ * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
+ * Currently fullscreen -> split snap still animates to center screen before readjusting.
*/
private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
@@ -891,12 +953,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopTasksController.ifPresent(
- c -> {
- c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- FINAL_FREEFORM_SCALE));
- });
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(),
+ DesktopTasksController
+ .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
}
});
animator.start();
@@ -904,7 +966,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
- if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ return null;
+ }
+ final boolean splitScreenVisible = mSplitScreenController != null
+ && mSplitScreenController.isSplitScreenVisible();
+ // It's possible that split tasks are visible but neither is focused, such as when there's
+ // a fullscreen translucent window on top of them. In that case, the relevant decor should
+ // just be that translucent focused window.
+ final boolean focusedTaskInSplit = mSplitScreenController != null
+ && mSplitScreenController.isTaskInSplitScreen(focusedDecor.mTaskInfo.taskId);
+ if (splitScreenVisible && focusedTaskInSplit) {
// We can't look at focused task here as only one task will have focus.
DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
@@ -938,7 +1011,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
@@ -1010,34 +1086,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
- final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
- windowDecoration);
+ final DragPositioningCallback dragPositioningCallback;
+ final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ dragPositioningCallback = new FluidResizeTaskPositioner(
+ mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
+ mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (FluidResizeTaskPositioner) dragPositioningCallback);
+ } else {
+ dragPositioningCallback = new VeiledResizeTaskPositioner(
+ mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener, mTransitions, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (VeiledResizeTaskPositioner) dragPositioningCallback);
+ }
+
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
windowDecoration.setCaptionListeners(
- touchEventListener, touchEventListener, touchEventListener);
+ touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
incrementEventReceiverTasks(taskInfo.displayId);
}
- private DragPositioningCallback createDragPositioningCallback(
- @NonNull DesktopModeWindowDecoration windowDecoration) {
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height);
- if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransactionFactory,
- transitionAreaHeight);
- } else {
- return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransitions,
- transitionAreaHeight);
- }
- }
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
@@ -1046,12 +1123,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return mSplitScreenController.getTaskInfo(remainingTaskPosition);
}
- private void closeOtherSplitTask(int taskId) {
- if (isTaskInSplitScreen(taskId)) {
- mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
- }
- }
-
private boolean isTaskInSplitScreen(int taskId) {
return mSplitScreenController != null
&& mSplitScreenController.isTaskInSplitScreen(taskId);
@@ -1066,6 +1137,36 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
}
+ private class DeskopModeOnTaskResizeAnimationListener
+ implements OnTaskResizeAnimationListener {
+ @Override
+ public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ t.apply();
+ return;
+ }
+ decoration.showResizeVeil(t, bounds);
+ decoration.setAnimatingTaskResize(true);
+ }
+
+ @Override
+ public void onBoundsChange(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.updateResizeVeil(t, bounds);
+ }
+
+ @Override
+ public void onAnimationEnd(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.hideResizeVeil();
+ decoration.setAnimatingTaskResize(false);
+ }
+ }
+
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
@@ -1086,12 +1187,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onExclusionRegionChanged(int taskId, Region region) {
- mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
+ mDesktopTasksController.onExclusionRegionChanged(taskId, region);
}
@Override
public void onExclusionRegionDismissed(int taskId) {
- mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
+ mDesktopTasksController.removeExclusionRegionForTask(taskId);
}
}
@@ -1138,7 +1239,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0e28dd..39803e2afd34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -36,8 +36,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.IBinder;
-import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -46,6 +44,7 @@ import android.view.ViewConfiguration;
import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -57,12 +56,13 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
-import java.util.HashSet;
-import java.util.Set;
+import kotlin.Unit;
+
import java.util.function.Supplier;
/**
@@ -82,6 +82,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
+ private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -103,8 +104,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private ExclusionRegionListener mExclusionRegionListener;
- private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
- private int mRelayoutBlock;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
@@ -157,10 +156,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
- View.OnLongClickListener onLongClickListener) {
+ View.OnLongClickListener onLongClickListener,
+ View.OnGenericMotionListener onGenericMotionListener) {
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
mOnCaptionTouchListener = onCaptionTouchListener;
mOnCaptionLongClickListener = onLongClickListener;
+ mOnCaptionGenericMotionListener = onGenericMotionListener;
}
void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
@@ -178,63 +179,34 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
- // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
- // and interferes with the transition animation that is playing at the same time.
- if (mRelayoutBlock > 0) {
- return;
- }
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
+ && mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
- final int shadowRadiusID = taskInfo.isFocused
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
-
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
- taskInfo.getWindowingMode());
- mRelayoutParams.reset();
- mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
- mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- // The configuration used to lay out the window decoration. The system context's config is
- // used when the task density has been overridden to a custom density so that the resources
- // and views of the decoration aren't affected and match the rest of the System UI, if not
- // then just use the task's configuration. A copy is made instead of using the original
- // reference so that the configuration isn't mutated on config changes and diff checks can
- // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
- // See b/301119301.
- // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
- // instead of using a whole Configuration as a parameter.
- final Configuration windowDecorConfig = new Configuration();
- windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
- ? mContext.getResources().getConfiguration() // Use system context.
- : mTaskInfo.configuration); // Use task configuration.
- mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
-
- mRelayoutParams.mCornerRadius =
- (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -259,9 +231,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
mOnCaptionLongClickListener,
+ mOnCaptionGenericMotionListener,
mAppName,
- mAppIconBitmap
- );
+ mAppIconBitmap,
+ () -> {
+ if (!isMaximizeMenuActive()) {
+ createMaximizeMenu();
+ }
+ return Unit.INSTANCE;
+ });
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
}
@@ -273,6 +251,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeMaximizeMenu();
}
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
if (!isDragResizeable) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
@@ -323,6 +304,80 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ Context context,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw,
+ boolean shouldSetTaskPositionAndCrop) {
+ final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+ relayoutParams.reset();
+ relayoutParams.mRunningTaskInfo = taskInfo;
+ relayoutParams.mLayoutResId = captionLayoutId;
+ relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+ relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
+
+ if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor
+ && TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ // App is requesting to customize the caption bar. Allow input to fall through to the
+ // windows below so that the app can respond to input events on their custom content.
+ relayoutParams.mAllowCaptionInputFallthrough = true;
+ // Report occluding elements as bounding rects to the insets system so that apps can
+ // draw in the empty space in the center:
+ // First, the "app chip" section of the caption bar (+ some extra margins).
+ final RelayoutParams.OccludingCaptionElement appChipElement =
+ new RelayoutParams.OccludingCaptionElement();
+ appChipElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_start;
+ appChipElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
+ relayoutParams.mOccludingCaptionElements.add(appChipElement);
+ // Then, the right-aligned section (drag space, maximize and close buttons).
+ final RelayoutParams.OccludingCaptionElement controlsElement =
+ new RelayoutParams.OccludingCaptionElement();
+ controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
+ controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
+ relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ }
+ if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) {
+ relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ }
+ relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+ // The configuration used to lay out the window decoration. The system context's config is
+ // used when the task density has been overridden to a custom density so that the resources
+ // and views of the decoration aren't affected and match the rest of the System UI, if not
+ // then just use the task's configuration. A copy is made instead of using the original
+ // reference so that the configuration isn't mutated on config changes and diff checks can
+ // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+ // See b/301119301.
+ // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+ // instead of using a whole Configuration as a parameter.
+ final Configuration windowDecorConfig = new Configuration();
+ windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+ ? context.getResources().getConfiguration() // Use system context.
+ : taskInfo.configuration); // Use task configuration.
+ relayoutParams.mWindowDecorConfig = windowDecorConfig;
+
+ if (DesktopModeStatus.useRoundedCorners()) {
+ relayoutParams.mCornerRadius =
+ (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+ }
+ }
+
+ /**
+ * If task has focused window decor, return the caption id of the fullscreen caption size
+ * resource. Otherwise, return ID_NULL and caption width be set to task width.
+ */
+ private static int getCaptionWidthId(int layoutResId) {
+ if (layoutResId == R.layout.desktop_mode_focused_window_decor) {
+ return R.dimen.desktop_mode_fullscreen_decor_caption_width;
+ }
+ return Resources.ID_NULL;
+ }
+
+
private PointF calculateMaximizeMenuPosition() {
final PointF position = new PointF();
final Resources resources = mContext.getResources();
@@ -364,24 +419,21 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return mHandleMenu != null;
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+ }
+
private void loadAppInfo() {
- String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
- try {
- final IconProvider provider = new IconProvider(mContext);
- mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
- PackageManager.ComponentInfoFlags.of(0)));
- final Resources resources = mContext.getResources();
- final BaseIconFactory factory = new BaseIconFactory(mContext,
- resources.getDisplayMetrics().densityDpi,
- resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
- mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
- final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- mAppName = pm.getApplicationLabel(applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package not found: " + packageName, e);
- }
+ final IconProvider provider = new IconProvider(mContext);
+ mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo);
+ final Resources resources = mContext.getResources();
+ final BaseIconFactory factory = new BaseIconFactory(mContext,
+ resources.getDisplayMetrics().densityDpi,
+ resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+ mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+ final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo;
+ mAppName = pm.getApplicationLabel(applicationInfo);
}
private void closeDragResizeListener() {
@@ -443,11 +495,72 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
- mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext,
+ mDisplayController, mTaskInfo, mOnCaptionButtonClickListener,
+ mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext,
calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
mMaximizeMenu.show();
}
@@ -475,7 +588,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
- .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
.setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
.setCaptionHeight(mResult.mCaptionHeight)
.build();
@@ -530,8 +642,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void closeMaximizeMenuIfNeeded(MotionEvent ev) {
if (!isMaximizeMenuActive()) return;
- final PointF inputPoint = offsetCaptionLocation(ev);
- if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ if (!mMaximizeMenu.isValidMenuInput(ev)) {
closeMaximizeMenu();
}
}
@@ -552,35 +663,46 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
if (taskInfo == null) return result;
final Point positionInParent = taskInfo.positionInParent;
- result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
return result;
}
/**
- * Determine if a passed MotionEvent is in a view in caption
+ * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+ * a task in fullscreen or in multi-windowing mode). This should be used in cases where
+ * onTouchListener will not work (i.e. when caption is in status bar area).
*
* @param ev the {@link MotionEvent} to check
- * @param layoutId the id of the view
- * @return {@code true} if event is inside the specified view, {@code false} if not
+ * @return {@code true} if event is inside caption handle view, {@code false} if not
*/
- private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
- if (mResult.mRootView == null) return false;
- final PointF inputPoint = offsetCaptionLocation(ev);
- final View view = mResult.mRootView.findViewById(layoutId);
- return view != null && pointInView(view, inputPoint.x, inputPoint.y);
- }
+ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
+ if (isHandleMenuActive() || !(mWindowDecorViewHolder
+ instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
+ return false;
+ }
- boolean checkTouchEventInHandle(MotionEvent ev) {
- if (isHandleMenuActive()) return false;
- return checkEventInCaptionView(ev, R.id.caption_handle);
+ return checkTouchEventInCaption(ev);
}
/**
- * Returns true if motion event is within the caption's root view's bounds.
+ * Checks if touch event occurs in caption.
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @return {@code true} if event is inside caption view, {@code false} if not
*/
boolean checkTouchEventInCaption(MotionEvent ev) {
- return checkEventInCaptionView(ev, getCaptionViewId());
+ final PointF inputPoint = offsetCaptionLocation(ev);
+ return inputPoint.x >= mResult.mCaptionX
+ && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+ && inputPoint.y >= 0
+ && inputPoint.y <= mResult.mCaptionHeight;
+ }
+
+ /**
+ * Checks whether the touch event falls inside the customizable caption region.
+ */
+ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
+ return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
}
/**
@@ -593,24 +715,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
if (!isHandleMenuActive()) {
+ // Click if point in caption handle view
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
- clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
+ if (checkTouchEventInFocusedCaptionHandle(ev)) {
+ mOnCaptionButtonClickListener.onClick(handle);
+ }
} else {
mHandleMenu.checkClickEvent(ev);
closeHandleMenuIfNeeded(ev);
}
}
- private boolean clickIfPointInView(PointF inputPoint, View v) {
- if (pointInView(v, inputPoint.x, inputPoint.y)) {
- mOnCaptionButtonClickListener.onClick(v);
- return true;
- }
- return false;
- }
-
- boolean pointInView(View v, float x, float y) {
+ private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
}
@@ -624,7 +741,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
super.close();
}
- private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
+ private static int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
@@ -658,18 +775,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return exclusionRegion;
}
- /**
- * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
- * mRelayoutBlock
- */
- void removeTransitionPausingRelayout(IBinder transition) {
- if (mTransitionsPausingRelayout.remove(transition)) {
- mRelayoutBlock--;
- }
- }
-
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return getCaptionHeightIdStatic(windowingMode);
+ }
+
+ private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FULLSCREEN
? R.dimen.desktop_mode_fullscreen_decor_caption_height
: R.dimen.desktop_mode_freeform_decor_caption_height;
@@ -684,35 +795,26 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return R.id.desktop_mode_caption;
}
- /**
- * Add transition to mTransitionsPausingRelayout
- */
- void addTransitionPausingRelayout(IBinder transition) {
- mTransitionsPausingRelayout.add(transition);
+ void setAnimatingTaskResize(boolean animatingTaskResize) {
+ if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) return;
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .setAnimatingTaskResize(animatingTaskResize);
}
- /**
- * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
- * remove the merged transition from the set and add the transition it was merged into.
- */
- public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
- if (mTransitionsPausingRelayout.remove(merged)) {
- mTransitionsPausingRelayout.add(playing);
- }
+ void onMaximizeWindowHoverExit() {
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .onMaximizeWindowHoverExit();
}
- /**
- * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
- */
- public void incrementRelayoutBlock() {
- mRelayoutBlock++;
+ void onMaximizeWindowHoverEnter() {
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .onMaximizeWindowHoverEnter();
}
@Override
public String toString() {
return "{"
+ "mPositionInParent=" + mPositionInParent + ", "
- + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ "taskId=" + mTaskInfo.taskId + ", "
+ "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ "isFocused=" + isFocused()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c733fe3..5afbd54088d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -26,9 +26,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.window.WindowContainerTransaction;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
/**
@@ -130,8 +128,7 @@ public class DragPositioningCallbackUtility {
Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t,
float x, float y) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y);
- t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left,
- repositionTaskBounds.top);
+ t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -162,31 +159,30 @@ public class DragPositioningCallbackUtility {
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
-
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
- }
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
}
- /**
- * Apply a bounds change to a task.
- * @param windowDecoration decor of task we are changing bounds for
- * @param taskBounds new bounds of this task
- * @param taskOrganizer applies the provided WindowContainerTransaction
- */
- static void applyTaskBoundsChange(WindowContainerTransaction wct,
- WindowDecoration windowDecoration, Rect taskBounds, ShellTaskOrganizer taskOrganizer) {
- wct.setBounds(windowDecoration.mTaskInfo.token, taskBounds);
- taskOrganizer.applyTransaction(wct);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ }
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21d4294..e83e5d1ef5a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,6 +49,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
+import android.window.InputTransferToken;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -74,7 +75,7 @@ class DragResizeInputListener implements AutoCloseable {
private final IBinder mClientToken;
- private final IBinder mFocusGrantToken;
+ private final InputTransferToken mInputTransferToken;
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
@@ -121,7 +122,7 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface = decorationSurface;
mDisplayController = displayController;
mClientToken = new Binder();
- mFocusGrantToken = new Binder();
+ mInputTransferToken = new InputTransferToken();
mInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
@@ -134,7 +135,7 @@ class DragResizeInputListener implements AutoCloseable {
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
- mFocusGrantToken,
+ mInputTransferToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
} catch (RemoteException e) {
@@ -169,7 +170,7 @@ class DragResizeInputListener implements AutoCloseable {
INPUT_FEATURE_NO_INPUT_CHANNEL,
TYPE_INPUT_CONSUMER,
null /* windowToken */,
- mFocusGrantToken,
+ mInputTransferToken,
"TaskInputSink of " + decorationSurface,
mSinkInputChannel);
} catch (RemoteException e) {
@@ -320,6 +321,10 @@ class DragResizeInputListener implements AutoCloseable {
}
}
+ boolean isHandlingDragResize() {
+ return mInputEventReceiver.isHandlingEvents();
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
@@ -349,6 +354,7 @@ class DragResizeInputListener implements AutoCloseable {
private boolean mShouldHandleEvents;
private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private Rect mDragStartTaskBounds;
+ private final Rect mTmpRect = new Rect();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -386,6 +392,10 @@ class DragResizeInputListener implements AutoCloseable {
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
}
+ boolean isHandlingEvents() {
+ return mShouldHandleEvents;
+ }
+
private boolean handleInputEvent(InputEvent inputEvent) {
if (!(inputEvent instanceof MotionEvent)) {
return false;
@@ -409,7 +419,6 @@ class DragResizeInputListener implements AutoCloseable {
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
- mInputManager.pilferPointers(mInputChannel.getToken());
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -427,6 +436,7 @@ class DragResizeInputListener implements AutoCloseable {
if (!mShouldHandleEvents) {
break;
}
+ mInputManager.pilferPointers(mInputChannel.getToken());
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +447,7 @@ class DragResizeInputListener implements AutoCloseable {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mInputManager.pilferPointers(mInputChannel.getToken());
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
final Rect taskBounds = mCallback.onDragPositioningEnd(
@@ -468,14 +479,15 @@ class DragResizeInputListener implements AutoCloseable {
}
private void updateInputSinkRegionForDrag(Rect taskBounds) {
+ mTmpRect.set(taskBounds);
final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
final Region dragTouchRegion = new Region(-taskBounds.left,
-taskBounds.top,
-taskBounds.left + layout.width(),
-taskBounds.top + layout.height());
// Remove the localized task bounds from the touch region.
- taskBounds.offsetTo(0, 0);
- dragTouchRegion.op(taskBounds, Region.Op.DIFFERENCE);
+ mTmpRect.offsetTo(0, 0);
+ dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
updateSinkInputChannel(dragTouchRegion);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e201b2..6bfc7cdcb33e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -16,23 +16,42 @@
package com.android.wm.shell.windowdecor;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
/**
* A task positioner that resizes/relocates task contents as it is dragged.
* Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds.
+ *
+ * This positioner applies the final bounds after a resize or drag using a shell transition in order
+ * to utilize the startAnimation callback to set the final task position and crop. In most cases,
+ * the transition will be aborted since the final bounds are usually the same bounds set in the
+ * final {@link #onDragPositioningMove} call. In this case, the cropping and positioning would be
+ * set by {@link WindowDecoration#relayout} due to the final bounds change; however, it is important
+ * that we send the final shell transition since we still utilize the {@link #onTransitionConsumed}
+ * callback.
*/
-class FluidResizeTaskPositioner implements DragPositioningCallback {
+class FluidResizeTaskPositioner implements DragPositioningCallback,
+ TaskDragResizer, Transitions.TransitionHandler {
private final ShellTaskOrganizer mTaskOrganizer;
+ private final Transitions mTransitions;
private final WindowDecoration mWindowDecoration;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private DisplayController mDisplayController;
@@ -45,21 +64,28 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
// finalize the bounds there using WCT#setBounds
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
+ private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
+ private IBinder mDragResizeEndTransition;
@Surface.Rotation private int mRotation;
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
- this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
- SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
+ WindowDecoration windowDecoration, DisplayController displayController,
+ int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, transitions, windowDecoration, displayController,
+ dragStartListener -> {}, SurfaceControl.Transaction::new,
+ disallowedAreaForEndBoundsHeight);
}
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
+ Transitions transitions,
+ WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier,
int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
+ mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
@@ -103,9 +129,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
// This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
}
- DragPositioningCallbackUtility.applyTaskBoundsChange(wct, mWindowDecoration,
- mRepositionTaskBounds, mTaskOrganizer);
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTaskOrganizer.applyTransaction(wct);
mHasDragResized = true;
+ mIsResizingOrAnimatingResize = true;
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mWindowDecoration,
@@ -129,16 +156,17 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mWindowDecoration)) {
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
- mTaskOrganizer.applyTransaction(wct);
+ mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
&& DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mTaskBoundsAtDragStart.setEmpty();
@@ -153,4 +181,51 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
+ startTransaction.apply();
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ }
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index 652a2ed39c67..b37dd0d6fd2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -64,8 +64,6 @@ class HandleMenu {
private final View.OnTouchListener mOnTouchListener;
private final RunningTaskInfo mTaskInfo;
private final int mLayoutResId;
- private final int mCaptionX;
- private final int mCaptionY;
private int mMarginMenuTop;
private int mMarginMenuStart;
private int mMenuHeight;
@@ -74,16 +72,13 @@ class HandleMenu {
private HandleMenuAnimator mHandleMenuAnimator;
- HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
- View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
- int captionHeight) {
+ HandleMenu(WindowDecoration parentDecor, int layoutResId, View.OnClickListener onClickListener,
+ View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName,
+ boolean shouldShowWindowingPill, int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
mLayoutResId = layoutResId;
- mCaptionX = captionX;
- mCaptionY = captionY;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
mAppIconBitmap = appIcon;
@@ -225,12 +220,12 @@ class HandleMenu {
if (mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- menuX = mCaptionX + mMarginMenuStart;
- menuY = mCaptionY + mMarginMenuTop;
+ menuX = mMarginMenuStart;
+ menuY = mMarginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- menuX = mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
- menuY = mCaptionY + mMarginMenuStart;
+ menuX = (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mMarginMenuStart;
}
// Handle Menu position setup.
@@ -346,8 +341,6 @@ class HandleMenu {
private View.OnClickListener mOnClickListener;
private View.OnTouchListener mOnTouchListener;
private int mLayoutId;
- private int mCaptionX;
- private int mCaptionY;
private boolean mShowWindowingPill;
private int mCaptionHeight;
@@ -381,12 +374,6 @@ class HandleMenu {
return this;
}
- Builder setCaptionPosition(int captionX, int captionY) {
- mCaptionX = captionX;
- mCaptionY = captionY;
- return this;
- }
-
Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
mShowWindowingPill = windowingButtonsVisible;
return this;
@@ -398,8 +385,8 @@ class HandleMenu {
}
HandleMenu build() {
- return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
+ return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener,
+ mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
new file mode 100644
index 000000000000..b2f8cfdbfb7a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ProgressBar
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import androidx.core.content.ContextCompat
+import com.android.wm.shell.R
+
+private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
+private const val MAX_DRAWABLE_ALPHA = 255
+
+class MaximizeButtonView(
+ context: Context,
+ attrs: AttributeSet
+) : FrameLayout(context, attrs) {
+ lateinit var onHoverAnimationFinishedListener: () -> Unit
+ private val hoverProgressAnimatorSet = AnimatorSet()
+ var hoverDisabled = false
+
+ private val progressBar: ProgressBar
+ private val maximizeWindow: ImageButton
+
+ init {
+ LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true)
+
+ progressBar = requireViewById(R.id.progress_bar)
+ maximizeWindow = requireViewById(R.id.maximize_window)
+ }
+
+ fun startHoverAnimation() {
+ if (hoverDisabled) return
+ if (hoverProgressAnimatorSet.isRunning) {
+ cancelHoverAnimation()
+ }
+
+ maximizeWindow.background.alpha = 0
+
+ hoverProgressAnimatorSet.playSequentially(
+ ValueAnimator.ofInt(0, MAX_DRAWABLE_ALPHA)
+ .setDuration(50)
+ .apply {
+ addUpdateListener {
+ maximizeWindow.background.alpha = animatedValue as Int
+ }
+ },
+ ObjectAnimator.ofInt(progressBar, "progress", 100)
+ .setDuration(OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS.toLong())
+ .apply {
+ doOnStart {
+ progressBar.setProgress(0, false)
+ progressBar.visibility = View.VISIBLE
+ }
+ doOnEnd {
+ progressBar.visibility = View.INVISIBLE
+ onHoverAnimationFinishedListener()
+ }
+ }
+ )
+ hoverProgressAnimatorSet.start()
+ }
+
+ fun cancelHoverAnimation() {
+ hoverProgressAnimatorSet.removeAllListeners()
+ hoverProgressAnimatorSet.cancel()
+ progressBar.visibility = View.INVISIBLE
+ }
+
+ fun setAnimationTints(darkMode: Boolean) {
+ if (darkMode) {
+ progressBar.progressTintList = ColorStateList.valueOf(
+ resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark))
+ maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_dark))
+ } else {
+ progressBar.progressTintList = ColorStateList.valueOf(
+ resources.getColor(R.color.desktop_mode_maximize_menu_progress_light))
+ maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_light))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708faab16..b82f7ca47ef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -16,17 +16,20 @@
package com.android.wm.shell.windowdecor
+import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.Resources
import android.graphics.PixelFormat
import android.graphics.PointF
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
-import android.view.View
import android.view.View.OnClickListener
+import android.view.View.OnGenericMotionListener
+import android.view.View.OnTouchListener
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
@@ -49,6 +52,8 @@ class MaximizeMenu(
private val displayController: DisplayController,
private val taskInfo: RunningTaskInfo,
private val onClickListener: OnClickListener,
+ private val onGenericMotionListener: OnGenericMotionListener,
+ private val onTouchListener: OnTouchListener,
private val decorWindowContext: Context,
private val menuPosition: PointF,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
@@ -62,6 +67,8 @@ class MaximizeMenu(
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
/** Position the menu relative to the caption's position. */
fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +102,6 @@ class MaximizeMenu(
.setName("Maximize Menu")
.setContainerLayer()
.build()
- val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
- val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
val lp = WindowManager.LayoutParams(
menuWidth,
menuHeight,
@@ -142,15 +147,26 @@ class MaximizeMenu(
private fun setupMaximizeMenu() {
val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
- maximizeMenuView.requireViewById<Button>(
+ maximizeMenuView.setOnGenericMotionListener(onGenericMotionListener)
+ maximizeMenuView.setOnTouchListener(onTouchListener)
+
+ val maximizeButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_maximize_button
- ).setOnClickListener(onClickListener)
- maximizeMenuView.requireViewById<Button>(
+ )
+ maximizeButton.setOnClickListener(onClickListener)
+ maximizeButton.setOnGenericMotionListener(onGenericMotionListener)
+
+ val snapRightButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_snap_right_button
- ).setOnClickListener(onClickListener)
- maximizeMenuView.requireViewById<Button>(
+ )
+ snapRightButton.setOnClickListener(onClickListener)
+ snapRightButton.setOnGenericMotionListener(onGenericMotionListener)
+
+ val snapLeftButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_snap_left_button
- ).setOnClickListener(onClickListener)
+ )
+ snapLeftButton.setOnClickListener(onClickListener)
+ snapLeftButton.setOnGenericMotionListener(onGenericMotionListener)
}
/**
@@ -160,14 +176,11 @@ class MaximizeMenu(
*
* @param inputPoint the input to compare against.
*/
- fun isValidMenuInput(inputPoint: PointF): Boolean {
- val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
- return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
- inputPoint.y - menuPosition.y)
- }
-
- private fun pointInView(v: View, x: Float, y: Float): Boolean {
- return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ fun isValidMenuInput(ev: MotionEvent): Boolean {
+ val x = ev.rawX
+ val y = ev.rawY
+ return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+ menuPosition.y <= y && menuPosition.y + menuHeight >= y)
}
/**
@@ -176,4 +189,12 @@ class MaximizeMenu(
private fun viewsLaidOut(): Boolean {
return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
}
+
+ companion object {
+ fun isMaximizeMenuView(@IdRes viewId: Int): Boolean {
+ return viewId == R.id.maximize_menu || viewId == R.id.maximize_menu_maximize_button ||
+ viewId == R.id.maximize_menu_snap_left_button ||
+ viewId == R.id.maximize_menu_snap_right_button
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
new file mode 100644
index 000000000000..09c62bfc9da2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+/**
+ * Listener that allows implementations of [TransitionHandler] to notify when an
+ * animation that is resizing a task is starting, updating, and finishing the animation.
+ */
+interface OnTaskResizeAnimationListener {
+ /**
+ * Notifies that a transition animation is about to be started with the given bounds.
+ */
+ fun onAnimationStart(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is expanding or shrinking the task to the given bounds.
+ */
+ fun onBoundsChange(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is about to be finished.
+ */
+ fun onAnimationEnd(taskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 368231e2d7f0..b0d3b5090ef0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -125,6 +125,7 @@ public class ResizeVeil {
relayout(taskBounds, t);
if (fadeIn) {
+ cancelAnimation();
mVeilAnimator = new ValueAnimator();
mVeilAnimator.setFloatValues(0f, 1f);
mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
@@ -210,15 +211,16 @@ public class ResizeVeil {
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(1, 0);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
+ cancelAnimation();
+ mVeilAnimator = new ValueAnimator();
+ mVeilAnimator.setFloatValues(1, 0);
+ mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ mVeilAnimator.addUpdateListener(animation -> {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
- t.setAlpha(mVeilSurface, 1 - animator.getAnimatedFraction());
+ t.setAlpha(mVeilSurface, 1 - mVeilAnimator.getAnimatedFraction());
t.apply();
});
- animator.addListener(new AnimatorListenerAdapter() {
+ mVeilAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -226,7 +228,7 @@ public class ResizeVeil {
t.apply();
}
});
- animator.start();
+ mVeilAnimator.start();
}
@ColorRes
@@ -240,10 +242,20 @@ public class ResizeVeil {
}
}
+ private void cancelAnimation() {
+ if (mVeilAnimator != null) {
+ mVeilAnimator.removeAllUpdateListeners();
+ mVeilAnimator.cancel();
+ }
+ }
+
/**
* Dispose of veil when it is no longer needed, likely on close of its container decor.
*/
void dispose() {
+ cancelAnimation();
+ mVeilAnimator = null;
+
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
new file mode 100644
index 000000000000..40421b599889
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+/**
+ * Holds the state of a drag resize.
+ */
+interface TaskDragResizer {
+
+ /**
+ * Returns true if task is currently being resized or animating the final transition after
+ * a resize is complete.
+ */
+ boolean isResizingOrAnimating();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0caaca5..5c69d5542227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -43,7 +43,7 @@ import java.util.function.Supplier;
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements DragPositioningCallback,
- Transitions.TransitionHandler {
+ TaskDragResizer, Transitions.TransitionHandler {
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -59,10 +59,12 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
@@ -71,12 +73,13 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
- mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
+ mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
@@ -91,7 +94,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
- mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -116,7 +118,12 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ if (!mIsResizingOrAnimatingResize) {
+ mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+ mIsResizingOrAnimatingResize = true;
+ } else {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ }
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -138,23 +145,21 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
- mTaskOrganizer.applyTransaction(wct);
- }
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
- mDesktopWindowDecoration.hideResizeVeil();
+ resetVeilIfVisible();
}
} else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
- DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
- mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -168,15 +173,32 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ private void resetVeilIfVisible() {
+ if (mIsResizingOrAnimatingResize) {
+ mDesktopWindowDecoration.hideResizeVeil();
+ mIsResizingOrAnimatingResize = false;
+ }
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
startTransaction.apply();
- mDesktopWindowDecoration.hideResizeVeil();
+ resetVeilIfVisible();
mCtrlType = CTRL_TYPE_UNDEFINED;
finishCallback.onTransitionFinished(null);
+ mIsResizingOrAnimatingResize = false;
return true;
}
@@ -190,4 +212,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
@NonNull TransitionRequestInfo request) {
return null;
}
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index ae1a3d914be3..01a6012ea314 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,9 +17,7 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
-import android.os.IBinder;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,34 +101,4 @@ public interface WindowDecorViewModel {
* @param taskInfo the info of the task
*/
void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-
- /**
- * Notifies that a shell transition is about to start. If the transition is of type
- * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
- * task after the transition has ended.
- *
- * @param transition the ready transaction
- * @param info of Transition to check if relayout needs to be paused for a task
- * @param change a change in the given transition
- */
- default void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- /**
- * Notifies that a shell transition is about to merge with another to give the window
- * decoration a chance to prepare for this merge.
- *
- * @param merged the transaction being merged
- * @param playing the transaction being merged into
- */
- default void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- /**
- * Notifies that a shell transition is about to finish to give the window decoration a chance
- * to clean up.
- *
- * @param transaction
- */
- default void onTransitionFinished(IBinder transaction) {}
-
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b7558c7d8..32c2d1e9b257 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -30,6 +31,7 @@ import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Binder;
import android.view.Display;
import android.view.InsetsSource;
@@ -49,7 +51,10 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Supplier;
/**
@@ -123,6 +128,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
+ TaskDragResizer mTaskDragResizer;
private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
@@ -178,6 +184,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
@@ -270,9 +283,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = taskBounds.width();
+ outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
+ ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
+ outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+ outResult.mCaptionHeight)
+ .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -280,15 +297,48 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
// Caption insets
- mCaptionInsetsRect.set(taskBounds);
if (mIsCaptionVisible) {
- mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+ // Caption inset is the full width of the task with the |captionHeight| and
+ // positioned at the top of the task bounds, also in absolute coordinates.
+ // So just reuse the task bounds and adjust the bottom coordinate.
+ mCaptionInsetsRect.set(taskBounds);
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + outResult.mCaptionHeight;
+
+ // Caption bounding rectangles: these are optional, and are used to present finer
+ // insets than traditional |Insets| to apps about where their content is occluded.
+ // These are also in absolute coordinates.
+ final Rect[] boundingRects;
+ final int numOfElements = params.mOccludingCaptionElements.size();
+ if (numOfElements == 0) {
+ boundingRects = null;
+ } else {
+ // The customizable region can at most be equal to the caption bar.
+ if (params.mAllowCaptionInputFallthrough) {
+ outResult.mCustomizableCaptionRegion.set(mCaptionInsetsRect);
+ }
+ boundingRects = new Rect[numOfElements];
+ for (int i = 0; i < numOfElements; i++) {
+ final OccludingCaptionElement element =
+ params.mOccludingCaptionElements.get(i);
+ final int elementWidthPx =
+ resources.getDimensionPixelSize(element.mWidthResId);
+ boundingRects[i] =
+ calculateBoundingRect(element, elementWidthPx, mCaptionInsetsRect);
+ // Subtract the regions used by the caption elements, the rest is
+ // customizable.
+ if (params.mAllowCaptionInputFallthrough) {
+ outResult.mCustomizableCaptionRegion.op(boundingRects[i],
+ Region.Op.DIFFERENCE);
+ }
+ }
+ }
+ // Add this caption as an inset source.
wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect,
+ boundingRects);
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ mCaptionInsetsRect, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
WindowInsets.Type.captionBar());
@@ -303,25 +353,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
if (isFullscreen) {
- // Setting the task crop to the width/height stops input events from being sent to
- // some regions of the app window. See b/300324920
- // TODO(b/296921174): investigate whether crop/position needs to be set by window
- // decorations at all when transition handlers are already taking ownership of the task
- // surface placement/crop, especially when in fullscreen where tasks cannot be
- // drag-resized by the window decoration.
- startT.setWindowCrop(mTaskSurface, null);
- finishT.setWindowCrop(mTaskSurface, null);
// Shadow is not needed for fullscreen tasks
shadowRadius = 0;
} else {
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
- finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
shadowRadius = loadDimension(resources, params.mShadowRadiusId);
}
+
+ if (params.mSetTaskPositionAndCrop) {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
+ .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
+ }
+
startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
- finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius);
+ finishT.setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
// When fluid resize is enabled, add a background to freeform tasks
@@ -348,11 +394,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+ new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
+ if (params.mAllowCaptionInputFallthrough) {
+ lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ } else {
+ lp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
mCaptionWindowManager);
@@ -368,6 +419,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
+ int elementWidthPx, @NonNull Rect captionRect) {
+ switch (element.mAlignment) {
+ case START -> {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ }
+ case END -> {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ }
+ }
+ throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
+ }
+
/**
* Checks if task has entered/exited immersive mode and requires a change in caption visibility.
*/
@@ -386,6 +451,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ void setTaskDragResizer(TaskDragResizer taskDragResizer) {
+ mTaskDragResizer = taskDragResizer;
+ }
+
private void setCaptionVisibility(View rootView, boolean visible) {
if (rootView == null) {
return;
@@ -533,7 +602,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
- captionInsets);
+ captionInsets, null /* boundingRects */);
}
static class RelayoutParams {
@@ -541,35 +610,51 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mLayoutResId;
int mCaptionHeightId;
int mCaptionWidthId;
- int mShadowRadiusId;
+ final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
+ boolean mAllowCaptionInputFallthrough;
+ int mShadowRadiusId;
int mCornerRadius;
- int mCaptionX;
- int mCaptionY;
-
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
+ boolean mSetTaskPositionAndCrop;
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
- mShadowRadiusId = Resources.ID_NULL;
+ mOccludingCaptionElements.clear();
+ mAllowCaptionInputFallthrough = false;
+ mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
- mCaptionX = 0;
- mCaptionY = 0;
-
mApplyStartTransactionOnDraw = false;
+ mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
}
+
+ /**
+ * Describes elements within the caption bar that could occlude app content, and should be
+ * sent as bounding rectangles to the insets system.
+ */
+ static class OccludingCaptionElement {
+ int mWidthResId;
+ Alignment mAlignment;
+
+ enum Alignment {
+ START, END
+ }
+ }
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mCaptionHeight;
+ int mCaptionWidth;
+ int mCaptionX;
+ final Region mCustomizableCaptionRegion = Region.obtain();
int mWidth;
int mHeight;
T mRootView;
@@ -578,6 +663,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mWidth = 0;
mHeight = 0;
mCaptionHeight = 0;
+ mCaptionWidth = 0;
+ mCaptionX = 0;
+ mCustomizableCaptionRegion.setEmpty();
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
new file mode 100644
index 000000000000..5dd96aceaec7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.extension
+
+import android.app.TaskInfo
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
+
+val TaskInfo.isTransparentCaptionBarAppearance: Boolean
+ get() {
+ val appearance = taskDescription?.statusBarAppearance ?: 0
+ return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
+ }
+
+val TaskInfo.isLightCaptionBarAppearance: Boolean
+ get() {
+ val appearance = taskDescription?.statusBarAppearance ?: 0
+ return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
+ }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a8134c2d3..58bbb030da01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -12,6 +12,7 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.withStyledAttributes
+import androidx.core.view.isVisible
import com.android.internal.R.attr.materialColorOnSecondaryContainer
import com.android.internal.R.attr.materialColorOnSurface
import com.android.internal.R.attr.materialColorSecondaryContainer
@@ -19,6 +20,9 @@ import com.android.internal.R.attr.materialColorSurfaceContainerHigh
import com.android.internal.R.attr.materialColorSurfaceContainerLow
import com.android.internal.R.attr.materialColorSurfaceDim
import com.android.wm.shell.R
+import com.android.wm.shell.windowdecor.MaximizeButtonView
+import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance
+import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
/**
* A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
@@ -30,8 +34,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: View.OnClickListener,
onLongClickListener: OnLongClickListener,
+ onCaptionGenericMotionListener: View.OnGenericMotionListener,
appName: CharSequence,
- appIconBitmap: Bitmap
+ appIconBitmap: Bitmap,
+ onMaximizeHoverAnimationFinishedListener: () -> Unit
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -39,9 +45,13 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+ private val maximizeButtonView: MaximizeButtonView =
+ rootView.requireViewById(R.id.maximize_button_view)
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -51,10 +61,13 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnTouchListener(onCaptionTouchListener)
+ maximizeWindowButton.setOnGenericMotionListener(onCaptionGenericMotionListener)
maximizeWindowButton.onLongClickListener = onLongClickListener
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
appIconImageView.setImageBitmap(appIconBitmap)
+ maximizeButtonView.onHoverAnimationFinishedListener =
+ onMaximizeHoverAnimationFinishedListener
}
override fun bindData(taskInfo: RunningTaskInfo) {
@@ -64,19 +77,41 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
closeWindowButton.imageTintList = ColorStateList.valueOf(color)
maximizeWindowButton.imageTintList = ColorStateList.valueOf(color)
expandMenuButton.imageTintList = ColorStateList.valueOf(color)
+ appNameTextView.isVisible = !taskInfo.isTransparentCaptionBarAppearance
appNameTextView.setTextColor(color)
appIconImageView.imageAlpha = alpha
maximizeWindowButton.imageAlpha = alpha
closeWindowButton.imageAlpha = alpha
expandMenuButton.imageAlpha = alpha
+
+ maximizeButtonView.setAnimationTints(isDarkMode())
}
override fun onHandleMenuOpened() {}
override fun onHandleMenuClosed() {}
+ fun setAnimatingTaskResize(animatingTaskResize: Boolean) {
+ // If animating a task resize, cancel any running hover animations
+ if (animatingTaskResize) {
+ maximizeButtonView.cancelHoverAnimation()
+ }
+ maximizeButtonView.hoverDisabled = animatingTaskResize
+ }
+
+ fun onMaximizeWindowHoverExit() {
+ maximizeButtonView.cancelHoverAnimation()
+ }
+
+ fun onMaximizeWindowHoverEnter() {
+ maximizeButtonView.startHoverAnimation()
+ }
+
@ColorInt
private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int {
+ if (taskInfo.isTransparentCaptionBarAppearance) {
+ return Color.TRANSPARENT
+ }
val materialColorAttr: Int =
if (isDarkMode()) {
if (!taskInfo.isFocused) {
@@ -100,6 +135,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
@ColorInt
private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int {
val materialColorAttr = when {
+ taskInfo.isTransparentCaptionBarAppearance &&
+ taskInfo.isLightCaptionBarAppearance -> materialColorOnSecondaryContainer
+ taskInfo.isTransparentCaptionBarAppearance &&
+ !taskInfo.isLightCaptionBarAppearance -> materialColorOnSurface
isDarkMode() -> materialColorOnSurface
else -> materialColorOnSecondaryContainer
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 4930cb721336..6dcae2776847 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -35,9 +35,6 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
}
override fun bindData(taskInfo: RunningTaskInfo) {
- taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
- captionView.setBackgroundColor(captionColor)
- }
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 690b4e4be122..81bc34c876b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -17,9 +17,9 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
*/
abstract fun bindData(taskInfo: RunningTaskInfo)
- /** Callback when the handle menu is opened. */
- abstract fun onHandleMenuOpened()
+ /** Callback when the handle menu is opened. */
+ abstract fun onHandleMenuOpened()
- /** Callback when the handle menu is closed. */
- abstract fun onHandleMenuClosed()
+ /** Callback when the handle menu is closed. */
+ abstract fun onHandleMenuClosed()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index adf92d8854ff..3380adac0b3f 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTestData
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.FlickerTestData
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.helpers.LetterboxAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 1e5e42fb077e..f08eba5a73a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index 2fa1ec386781..826fc541687e 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index b74aa1d7bf73..26e78bf625ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -18,15 +18,15 @@ package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.common.datatypes.Rect
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.datatypes.Rect
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index 68fa8d2fc2e8..2aa84b4e55b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index fcb6931af9a2..443fac19c7e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
index 446aad8a8936..7ffa23345589 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -19,17 +19,17 @@ package com.android.wm.shell.flicker.appcompat
import android.os.Build
import android.platform.test.annotations.Postsubmit
import android.system.helpers.CommandsHelper
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.common.datatypes.Rect
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.FIND_TIMEOUT
-import android.tools.device.traces.parsers.toFlickerComponent
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.datatypes.Rect
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.FIND_TIMEOUT
+import android.tools.traces.parsers.toFlickerComponent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
index 9792c859cced..2c0f837248f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.tools.device.flicker.legacy.FlickerTestData
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.helpers.FIND_TIMEOUT
-import android.tools.device.traces.parsers.toFlickerComponent
+import android.tools.flicker.legacy.FlickerTestData
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.helpers.FIND_TIMEOUT
+import android.tools.traces.parsers.toFlickerComponent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 0c36e29a8315..8eca45694d48 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -21,12 +21,12 @@ import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.ServiceManager
-import android.tools.common.Rotation
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTestData
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.SYSTEMUI_PACKAGE
+import android.tools.Rotation
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.FlickerTestData
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.SYSTEMUI_PACKAGE
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 55039f59190b..bc486c277aa5 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.FlakyTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 9ca7bf113589..521c0d0aaeb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.bubble
import android.content.Context
import android.graphics.Point
import android.platform.test.annotations.Presubmit
-import android.tools.common.flicker.subject.layers.LayersTraceSubject
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.test.uiautomator.By
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index b007e6b3535c..e059ac78dc6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Postsubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.filters.FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 4959672d865b..ef7fbfb79beb 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 0d95574aca06..87224b151b78 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index e61f7629f4fd..faeb342a44be 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -30,6 +30,7 @@ filegroup {
"src/**/B*.kt",
"src/**/C*.kt",
"src/**/D*.kt",
+ "src/**/F*.kt",
"src/**/S*.kt",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index af2db12faf3a..d64bfed382b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
@@ -46,7 +46,7 @@ import org.junit.runners.Parameterized
* ```
* 1. All assertions are inherited from [EnterPipTest]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
new file mode 100644
index 000000000000..a0edcfb17971
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test auto entering pip using a source rect hint.
+ *
+ * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Press "Set SourceRectHint" to create a temporary view that is used as the source rect hint
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. All assertions are inherited from [AutoEnterPipOnGoToHomeTest]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
+ AutoEnterPipOnGoToHomeTest(flicker) {
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.setSourceRectHint()
+ pipApp.enableAutoEnterForPipActivity()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun pipOverlayNotShown() {
+ val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+ flicker.assertLayers {
+ this.notContains(overlay)
+ }
+ }
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // we don't use overlay when entering with sourceRectHint
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // TODO (b/323511194): Looks like there is some bounciness with pip when using
+ // auto enter and sourceRectHint that causes the app to move outside of the display
+ // bounds during the transition.
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 9256725c6180..031acf4919eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,7 +44,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 002c019eff93..860307f2bb76 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,7 +44,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 4cc9547ba2e3..c5541613fece 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 8207b85c3e0c..9a1bd267ea1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.pip
import android.app.Activity
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
@@ -60,7 +60,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
@@ -86,6 +86,8 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
+
+ pipApp.tapPipToShowMenu(wmHelper)
}
}
@@ -194,6 +196,28 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
}
}
+ @Postsubmit
+ @Test
+ fun menuOverlayMatchesTaskSurface() {
+ flicker.assertLayersEnd {
+ val pipAppRegion = visibleRegion(pipApp)
+ val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY)
+ pipAppRegion.coversExactly(pipMenuRegion.region)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ // during the transition we assert the center point is within the display bounds, since it
+ // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
+ // and once the animation is over we assert that it's fully within the display bounds, at
+ // which point the device also performs orientation change from landscape to portrait
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+ regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
+ }
+ }
+
/** {@inheritDoc} */
@FlakyTest(bugId = 267424412)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index cc943678d492..f97d8d1842b0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 7da442901e40..47bf41814d17 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 0ad9e4c61d83..a356e68d14dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -42,7 +42,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 89a6c93e478c..25614ef63ccc 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -46,7 +46,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 8978af0088b8..1964e3cebc89 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index a5c2c8988e70..b94989d98e97 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -17,14 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.toFlickerComponent
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
+import android.tools.traces.parsers.toFlickerComponent
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -53,7 +52,7 @@ import org.junit.runners.Parameterized
* ```
* 1. All assertions are inherited from [EnterPipTest]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
@@ -62,7 +61,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
+class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
@@ -120,9 +119,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
override fun pipAppLayerAlwaysVisible() {
- // pip layer in gesture nav will disappear during transition with alpha animation
- Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
- super.pipAppLayerAlwaysVisible()
+ // pip layer in should disappear during transition with alpha animation
}
@Presubmit
@@ -151,8 +148,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
@JvmStatic
fun getParams() =
LegacyFlickerTestFactory.nonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ supportedRotations = listOf(Rotation.ROTATION_0)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
new file mode 100644
index 000000000000..1ccc7d8084a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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 android.platform.test.annotations.Presubmit
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
+import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Open all apps and drag another app icon to enter split screen
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * 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)
+class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
+ EnterPipTransition(flicker) {
+ private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+ /** Second app used to enter split screen mode */
+ private val secondAppForSplitScreen =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
+
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ secondAppForSplitScreen.launchViaIntent(wmHelper)
+ pipApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ pipApp,
+ secondAppForSplitScreen,
+ flicker.scenario.startRotation
+ )
+ pipApp.enableEnterPipOnUserLeaveHint()
+ }
+ teardown {
+ pipApp.exit(wmHelper)
+ secondAppForSplitScreen.exit(wmHelper)
+ }
+ transitions { tapl.goHome() }
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerReduces() {
+ // when entering from split screen we use alpha animation, so there is no size change
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerReduces()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ // pip layer in should disappear during transition with alpha animation
+ }
+
+ @Presubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppWindowVisibleChanges() {
+ // TODO(b/322394235) this method comes from EnterPipOnUserLeaveHintTest, but due to how
+ // it is being packaged in Android.bp we cannot inherit from it. Needs to be refactored.
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertWm {
+ this.isAppWindowVisible(pipApp)
+ .then()
+ .isAppWindowInvisible(pipApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(pipApp, isOptional = true)
+ }
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppWindowAlwaysVisible() {
+ // TODO(b/322394235) this method comes from EnterPipOnUserLeaveHintTest, but due to how
+ // it is being packaged in Android.bp we cannot inherit from it. Needs to be refactored.
+ // In gestural nav the pip will first move behind home and then above home. The visual
+ // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+ // But the internal states of activity don't need to follow that, such as a temporary
+ // visibility state can be changed quickly outside a transaction so the test doesn't
+ // detect that. Hence, skip the case to avoid restricting the internal implementation.
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppWindowAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipWindowRemainInsideVisibleBounds() {
+ if (tapl.isTablet) {
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ } else {
+ // on phones home screen does not rotate in landscape, PiP enters back to portrait
+ // orientation - if we go from landscape to portrait it should switch between the bounds
+ // otherwise it should be the same as tablet (i.e. portrait to portrait)
+ if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
+ flicker.assertWmVisibleRegion(pipApp) {
+ // first check against landscape bounds then against portrait bounds
+ coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
+ }
+ } else {
+ // always check against the display bounds which do not change during transition
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 4776206724cc..9b746224a1a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
@@ -47,7 +47,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index 425cbfaffedd..e184cf04e4ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -17,14 +17,14 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.pip.common.PipTransition
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 18f30d96938b..490ebd190ee8 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
@@ -47,7 +47,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 36047cca55ea..70be58f06548 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index c7f2786debd0..a4df69fc6539 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index cabc1cc0b023..90b9798c6329 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.graphics.Rect
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 381f947cbc84..68417066ac0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.subject.exceptions.IncorrectRegionException
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 1f69847e5481..9a6dacb187ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.pip
import android.app.Activity
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 308ece40402f..d2f803ec9352 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.pip.common.PipTransition
@@ -50,7 +50,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 182a9089d040..c9f4a6ca75b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.pip.apps
import android.platform.test.annotations.Postsubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.flicker.junit.FlickerBuilderProvider
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerBuilderProvider
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Test
import org.junit.runners.Parameterized
@@ -101,7 +101,9 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
override fun pipLayerReduces() {
flicker.assertLayers {
val pipLayerList =
- this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible }
+ this.layers {
+ standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
+ }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index e272958d78f8..88650107e63a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -26,9 +26,9 @@ import android.os.Looper
import android.os.SystemClock
import android.platform.test.annotations.Postsubmit
import android.tools.device.apphelpers.MapsAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
@@ -137,6 +137,16 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+ /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ // For Maps the transition goes through the UI mode change that adds a snapshot overlay so
+ // we assert only start/end layers matching the app instead.
+ flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+ }
+
@Postsubmit
@Test
override fun focusChanges() {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 32f12592135d..9b5153875987 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -18,14 +18,14 @@ package com.android.wm.shell.flicker.pip.apps
import android.Manifest
import android.platform.test.annotations.Postsubmit
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.NetflixAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import org.junit.Assume
@@ -51,7 +51,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
@@ -69,7 +69,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+ NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
)
standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 509b32c11afe..3ae5937df4d0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip.apps
import android.Manifest
import android.platform.test.annotations.Postsubmit
-import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.YouTubeAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -47,7 +47,7 @@ import org.junit.runners.Parameterized
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index 751f2bc0807f..dc122590388f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 9c129e47efba..3d9eae62b499 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index 9450bdd2d894..7b6839dc123f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
index 7e42bc11a9c1..f4baf5f75928 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.flicker.subject.region.RegionSubject
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.Rotation
+import android.tools.flicker.subject.region.RegionSubject
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.wm.shell.flicker.utils.Direction
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 7b7f1d7b5a82..fd467e32e0dc 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.pip.common
import android.app.Instrumentation
import android.content.Intent
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import android.tools.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
index c6cbcd052fe0..4e1a8ff313cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.flicker.pip.tv
import android.app.Instrumentation
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiObject2
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8de377e..285422236c64 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -20,8 +20,8 @@ import android.app.ActivityManager
import android.app.IActivityManager
import android.app.IProcessObserver
import android.os.SystemClock
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.helpers.wakeUpAndGoToHomeScreen
+import android.tools.traces.parsers.WindowManagerStateHelper
import android.view.Surface.ROTATION_0
import android.view.Surface.rotationToString
import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
@@ -78,6 +78,14 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
uiAutomation.dropShellPermissionIdentity()
}
+ override fun onProcessStarted(
+ pid: Int,
+ processUid: Int,
+ packageUid: Int,
+ packageName: String,
+ processName: String
+ ) {}
+
override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
index 4bd79546b96d..4c6c6cce0105 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -20,13 +20,13 @@ import android.app.Instrumentation
import android.platform.test.rule.NavigationModeRule
import android.platform.test.rule.PressHomeRule
import android.platform.test.rule.UnlockScreenRule
-import android.tools.common.NavBar
-import android.tools.common.Rotation
+import android.tools.NavBar
+import android.tools.Rotation
import android.tools.device.apphelpers.MessagingAppHelper
-import android.tools.device.flicker.rules.ArtifactSaverRule
-import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
-import android.tools.device.flicker.rules.LaunchAppRule
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.tools.flicker.rules.ArtifactSaverRule
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.flicker.rules.LaunchAppRule
+import android.tools.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.rules.RuleChain
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
index a5c512267508..1684a26ac3d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
index 092fb6720e57..3b5fad60d8ee 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 69499b9b488b..2b8a90305d90 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
index bd627f4babaa..b284fe1caad5 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index a8f4d0a24c7e..a400ee44caa5 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index cee9bbfb4aa4..7f5ee4c2cdda 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
index c1b3aade11cd..1b075c498bc0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
index c6e2e854ab89..6ca373714f8a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 169b5cfa3462..f7d231f02935 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 412c011a3f17..ab819fad292a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 6e4cf9f55cfd..a6b732c47ea2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index cc2870213e8d..07e5f4b0b472 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 736604f02377..272569456d7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 8df8dfaab071..58cc4d70fde4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 378f055ef830..85897a136e33 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b33d26288d33..891b6df89b45 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index b1d3858b9dbb..798365218b04 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 6d824c74c1fe..1bdea66fc596 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index f1d3d0cf2716..bab0c0aa1e6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index a867bac8c0eb..17a59ab8a173 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 76247ba10037..2c36d647b719 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index e179da81e4db..6e91d047e64b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 20f554f7d154..a921b4663d09 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index f7776ee3244a..05f8912f6f47 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 4ff0b4362e60..1ae1f53b9bc1 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 930f31d1f348..e14ca550c84d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index 3da61e5e310c..ce0c4c456587 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 627ae1843314..5a8d2d51bec4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index c744103d49ba..d44261549544 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index 11a4e02c5e37..ddc8a0697beb 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index e37d806c7a14..64293b288a2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 2a50912e0a5c..517ba2dfc164 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index d5da1a8b558c..1bafe3b0898c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 7fdcb9be62ee..fd0100fd6c21 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 308e954b86c1..850b3d8f9867 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 39e75bd25a71..0b752bf7f58e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index e18da17175c0..3c52aa71eb9d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 00d60e756ffa..c2e21b89a480 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index d7efbc8c0fd4..bf85ab44df5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 4eece3f62d10..0ac4ca20e303 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index d96b056d8753..80bd088a192f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 809b690e0861..0dffb4af8d41 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index bbdf2d728494..b721f2fe294a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 5c29fd8fe57e..22cbc77d024b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index a7398ebf56e8..ac0f9e25285d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index eae88ad4ad09..f7a229d5ff16 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index 7e8ee04a28fa..6dbbcb0fcfb5 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 9295c330b879..bd69ea98a67b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 4b59e9fbd866..404b96fafd24 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 5ff36d4aabbb..a79687ddf68f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index c0cb7219437b..b52eb4cd6533 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index 8c140884aa50..d79620c73132 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 7b6614b81c11..d27bfa1a22c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index 5df5be9daa8b..3c7d4d4806cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 9d63003bf2a1..26a2034f16d9 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 9fa04b208ad1..5154b35ed0e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index 9386aa2b2cf0..86451c524ee6 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 5ef21672bfe0..baf72b40d6d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
+import android.tools.Rotation
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 824e45403d2a..89ef91e12758 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index 4c391047e853..c1a8ee714abd 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index f6d1afc05a5a..600855a8ab38 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index db5a32a382fb..c671fbe39ac5 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 03170a326890..a189325d52ea 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index c52ada3b312f..433669205834 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 479d01ddaeb9..8c7e63f7471f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 625c56bc4a4c..2072831d7d1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index f1a011c0d191..09e77ccffba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index c9b1c916ff4b..babdae164835 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
import android.graphics.Point
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.helpers.WindowUtils
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 72f2db3380dd..3e8547961ea0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index 511de4fd8b90..655ae4e29af3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 558d2bf1f349..22082586bb62 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index ecd68295df9a..2ac63c2afefc 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index f50d5c7df8d7..35b122d7bc9e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.AndroidLoggerSetupRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.AndroidLoggerSetupRule
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 6b971699d212..d74c59ef0879 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.EdgeExtensionComponentMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.component.EdgeExtensionComponentMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 51588569a8aa..dd45f654d3bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.helpers.WindowUtils
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.helpers.WindowUtils
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index fc6c2b3d7ce7..6d396ea6e9d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 8b1689a9d816..2ed916e56c67 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 99613f39060d..1a455311b3b6 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 756a7fa4ba98..0cb1e4006c0d 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 121b46acdb66..ff406b75b235 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 99deb9279271..2b817988a589 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 212a4e3649dc..186af54fb57b 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index fac97c8cc8c4..9dde49011ed0 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 284c32ea110d..5222b08240c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9e6448f0bec9..a8a8ae88a9e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 8e28712cd993..836f664ca544 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index fb0193b1830d..3c4a1caecb8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
index 715a533a7bab..8724346427f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index f3145c97a6f1..16d73318bd3a 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -18,14 +18,14 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.flicker.subject.layers.LayersTraceSubject
-import android.tools.common.flicker.subject.region.RegionSubject
-import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.subject.layers.LayersTraceSubject
+import android.tools.flicker.subject.region.RegionSubject
+import android.tools.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
@@ -113,7 +113,6 @@ class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) :
subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
}
.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }
- .toTypedArray()
val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp)
it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region)
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index df1c9a2ec089..9c5a3fe35bfe 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index d01eab060263..c99fcc4129d5 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index e36bd33bd1fd..ef3a87955bd6 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 050d389e978c..18550d7f0467 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 5c43cbdb3832..d16c5d77410c 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index cd3fbab1497b..f8be6be08782 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 15ad0c12c49a..a99ef64e7bf5 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index ca8adb1fcb38..f58400966531 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 5e5e7d7fc3c9..7084f6aec1fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
index a0e437c25aa7..4b106034b2b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
import android.content.Context
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseBenchmarkTest
import com.android.wm.shell.flicker.utils.SplitScreenUtils
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index e39c3c93d79a..38206c396efb 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,14 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.helpers.WindowUtils
+import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 32284ba41aee..3a2316f7a10c 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a926ec903f58..ded0b0729998 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index d2e1d5294aa1..7b1397baa7a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 9d6b25174c13..457288f445df 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index e71834de7123..7493538fa2ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.NavBar
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index e9363f725fd8..d03c2f1077bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker
import android.app.Instrumentation
-import android.tools.device.flicker.junit.FlickerBuilderProvider
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.junit.FlickerBuilderProvider
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 568650d71872..a19d232c9a2f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker
import android.app.Instrumentation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.LegacyFlickerTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index f1cb37ee1293..3df0954da2e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -18,13 +18,13 @@
package com.android.wm.shell.flicker.utils
-import android.tools.common.Rotation
-import android.tools.common.datatypes.Region
-import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
-import android.tools.common.flicker.subject.layers.LayersTraceSubject
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.helpers.WindowUtils
+import android.tools.Rotation
+import android.tools.datatypes.Region
+import android.tools.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.flicker.subject.layers.LayersTraceSubject
+import android.tools.traces.component.IComponentMatcher
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.helpers.WindowUtils
fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
index 3b66d6addacd..fb21fcceea6e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
@@ -18,7 +18,7 @@
package com.android.wm.shell.flicker.utils
-import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.traces.component.ComponentNameMatcher
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 7f58cedce63d..50c04354528f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker.utils
import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 3244ebc9ef32..4e9a9d65dbf9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.Rotation
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.component.IComponentMatcher
+import android.tools.traces.component.IComponentNameMatcher
import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
-import android.tools.device.traces.parsers.WindowManagerStateHelper
-import android.tools.device.traces.parsers.toFlickerComponent
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
import android.view.InputDevice
import android.view.MotionEvent
import android.view.ViewConfiguration
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index aadadd604d3e..32c070305e05 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -19,11 +19,15 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_multitasking_windowing",
}
android_test {
name: "WMShellUnitTests",
-
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
srcs: [
"**/*.java",
"**/*.kt",
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index 47a116be1b66..2ef425cf3d41 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
<application android:debuggable="true" android:largeHeap="true">
<uses-library android:name="android.test.mock" />
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 771876f7ce5d..9ded6ea1d187 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,6 +63,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
@@ -110,6 +111,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private InputManager mInputManager;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -145,7 +148,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -298,7 +302,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 874ef80c29f0..7e26577e96d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.back;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import android.os.Handler;
import android.os.Looper;
@@ -28,6 +29,7 @@ import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +55,7 @@ public class BackProgressAnimatorTest {
/* progress = */ progress,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
}
@@ -101,6 +104,36 @@ public class BackProgressAnimatorTest {
assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
}
+ @Test
+ public void testResetCallsCancelCallbackImmediately() throws InterruptedException {
+ // Give the animator some progress.
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+
+ mTargetProgress = 0;
+ mReceivedBackEvent = null;
+ mTargetProgressCalled = new CountDownLatch(1);
+
+ CountDownLatch cancelCallbackCalled = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mProgressAnimator.onBackCancelled(cancelCallbackCalled::countDown));
+
+ // verify onBackProgressed and onBackCancelled not yet called
+ assertNull(mReceivedBackEvent);
+ assertEquals(1, cancelCallbackCalled.getCount());
+
+ // call reset
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset());
+
+ // verify that back event with progress 0 is sent and cancel callback is invoked
+ assertNotNull(mReceivedBackEvent);
+ assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+ assertEquals(0, cancelCallbackCalled.getCount());
+ }
+
private void onGestureProgress(BackEvent backEvent) {
if (mTargetProgress == backEvent.getProgress()) {
mReceivedBackEvent = backEvent;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
index bf07dccd0658..6dbb1e2b8d92 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -170,6 +170,71 @@ class TouchTrackerTest {
nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget)
}
+ @Test
+ fun restartingGesture_resetsInitialTouchX_leftEdge() {
+ val linearTracker = linearTouchTracker()
+ linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+ var touchX = 100f
+ val velocityX = 0f
+ val velocityY = 0f
+
+ // assert that progress is increased when increasing touchX
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+ // assert that progress is reset to 0 when start location is updated
+ linearTracker.updateStartLocation()
+ linearTracker.assertProgress(0f)
+
+ // assert that progress remains 0 when touchX is decreased
+ touchX -= 50
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress(0f)
+
+ // assert that progress uses new minimal touchX for progress calculation
+ val newInitialTouchX = touchX
+ touchX += 100
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+
+ // assert the same for triggerBack==true
+ linearTracker.triggerBack = true
+ linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+ }
+
+ @Test
+ fun restartingGesture_resetsInitialTouchX_rightEdge() {
+ val linearTracker = linearTouchTracker()
+ linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT)
+
+ var touchX = INITIAL_X_RIGHT_EDGE - 100f
+ val velocityX = 0f
+ val velocityY = 0f
+
+ // assert that progress is increased when decreasing touchX
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / MAX_DISTANCE)
+
+ // assert that progress is reset to 0 when start location is updated
+ linearTracker.updateStartLocation()
+ linearTracker.assertProgress(0f)
+
+ // assert that progress remains 0 when touchX is increased
+ touchX += 50
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress(0f)
+
+ // assert that progress uses new maximal touchX for progress calculation
+ val newInitialTouchX = touchX
+ touchX -= 100
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+
+ // assert the same for triggerBack==true
+ linearTracker.triggerBack = true
+ linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+ }
+
companion object {
private const val MAX_DISTANCE = 500f
private const val LINEAR_DISTANCE = 400f
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index dab762f233e2..fa0aba5a6ee9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1190,6 +1190,23 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
+ @Test
+ public void test_removeOverflowBubble() {
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
+
+ mBubbleData.removeOverflowBubble(mBubbleA1);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
+ assertOverflowChangedTo(ImmutableList.of());
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index f5b0174642d1..094af9652ea3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -45,18 +44,22 @@ public class BubbleOverflowTest extends ShellTestCase {
private TestableBubblePositioner mPositioner;
private BubbleOverflow mOverflow;
+ private BubbleExpandedViewManager mExpandedViewManager;
@Mock
private BubbleController mBubbleController;
+ @Mock
+ private BubbleStackView mBubbleStackView;
@Before
- public void setUp() throws Exception {
+ public void setUp() {
MockitoAnnotations.initMocks(this);
+ mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController);
mPositioner = new TestableBubblePositioner(mContext,
mContext.getSystemService(WindowManager.class));
when(mBubbleController.getPositioner()).thenReturn(mPositioner);
- when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class));
+ when(mBubbleController.getStackView()).thenReturn(mBubbleStackView);
mOverflow = new BubbleOverflow(mContext, mPositioner);
}
@@ -65,7 +68,7 @@ public class BubbleOverflowTest extends ShellTestCase {
public void test_initialize_forStack() {
assertThat(mOverflow.getExpandedView()).isNull();
- mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+ mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
assertThat(mOverflow.getExpandedView()).isNotNull();
assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
@@ -74,7 +77,7 @@ public class BubbleOverflowTest extends ShellTestCase {
@Test
public void test_initialize_forBubbleBar() {
- mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+ mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner);
assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
assertThat(mOverflow.getExpandedView()).isNull();
@@ -82,11 +85,10 @@ public class BubbleOverflowTest extends ShellTestCase {
@Test
public void test_cleanUpExpandedState() {
- mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+ mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
assertThat(mOverflow.getExpandedView()).isNotNull();
mOverflow.cleanUpExpandedState();
assertThat(mOverflow.getExpandedView()).isNull();
}
-
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
deleted file mode 100644
index 6ebee730756e..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles;
-
-import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests operations and the resulting state managed by {@link BubblePositioner}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class BubblePositionerTest extends ShellTestCase {
-
- private BubblePositioner mPositioner;
-
- @Before
- public void setUp() {
- WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- mPositioner = new BubblePositioner(mContext, windowManager);
- }
-
- @Test
- public void testUpdate() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1000, 1200);
- Rect availableRect = new Rect(screenBounds);
- availableRect.inset(insets);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
- assertThat(mPositioner.isLandscape()).isFalse();
- assertThat(mPositioner.isLargeScreen()).isFalse();
- assertThat(mPositioner.getInsets()).isEqualTo(insets);
- }
-
- @Test
- public void testShowBubblesVertically_phonePortrait() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.showBubblesVertically()).isFalse();
- }
-
- @Test
- public void testShowBubblesVertically_phoneLandscape() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.isLandscape()).isTrue();
- assertThat(mPositioner.showBubblesVertically()).isTrue();
- }
-
- @Test
- public void testShowBubblesVertically_tablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.showBubblesVertically()).isTrue();
- }
-
- /** If a resting position hasn't been set, calling it will return the default position. */
- @Test
- public void testGetRestingPosition_returnsDefaultPosition() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- PointF restingPosition = mPositioner.getRestingPosition();
- PointF defaultPosition = mPositioner.getDefaultStartPosition();
-
- assertThat(restingPosition).isEqualTo(defaultPosition);
- }
-
- /** If a resting position has been set, it'll return that instead of the default position. */
- @Test
- public void testGetRestingPosition_returnsRestingPosition() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- PointF restingPosition = new PointF(100, 100);
- mPositioner.setRestingPosition(restingPosition);
-
- assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
- }
-
- /** Test that the default resting position on phone is in upper left. */
- @Test
- public void testGetRestingPosition_bubble_onPhone() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_bubble_onPhone_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- /** Test that the default resting position on tablet is middle left. */
- @Test
- public void testGetRestingPosition_chatBubble_onTablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_chatBubble_onTablet_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- /** Test that the default resting position on tablet is middle right. */
- @Test
- public void testGetDefaultPosition_appBubble_onTablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_appBubble_onTablet_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testHasUserModifiedDefaultPosition_false() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
- mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
- }
-
- @Test
- public void testHasUserModifiedDefaultPosition_true() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
- mPositioner.setRestingPosition(new PointF(0, 100));
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
- }
-
- @Test
- public void testGetExpandedViewHeight_max() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT);
- }
-
- @Test
- public void testGetExpandedViewHeight_customHeight_valid() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- final int minHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_default_height);
- Bubble bubble = new Bubble("key",
- mock(ShortcutInfo.class),
- minHeight + 100 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor(),
- mock(Bubbles.BubbleMetadataFlagListener.class));
-
- // Ensure the height is the same as the desired value
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(
- bubble.getDesiredHeight(mContext));
- }
-
-
- @Test
- public void testGetExpandedViewHeight_customHeight_tooSmall() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Bubble bubble = new Bubble("key",
- mock(ShortcutInfo.class),
- 10 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor(),
- mock(Bubbles.BubbleMetadataFlagListener.class));
-
- // Ensure the height is the same as the minimum value
- final int minHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_default_height);
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight);
- }
-
- @Test
- public void testGetMaxExpandedViewHeight_onLargeTablet() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- int manageButtonHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
- int pointerWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_pointer_width);
- int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R
- .dimen.bubble_expanded_view_padding);
- float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth
- - expandedViewPadding * 2;
- assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */)))
- .isWithin(0.1f).of(expectedHeight);
- }
-
- @Test
- public void testAreBubblesBottomAligned_largeScreen_true() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
- }
-
- @Test
- public void testAreBubblesBottomAligned_largeScreen_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testAreBubblesBottomAligned_smallTablet_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setSmallTablet()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testAreBubblesBottomAligned_phone_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testExpandedViewY_phoneLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height so it'll always be top aligned
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_phonePortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // Always top aligned in phone portrait
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_smallTabletLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setSmallTablet()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_smallTabletPortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setSmallTablet()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_largeScreenLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on landscape, large tablet
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_largeScreenPortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- int manageButtonHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
- int manageButtonPlusMargin = manageButtonHeight + 2
- * mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_manage_button_margin);
- int pointerWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_pointer_width);
-
- final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
- - manageButtonPlusMargin
- - mPositioner.getExpandedViewHeightForLargeScreen()
- - pointerWidth;
-
- // Bubbles are bottom aligned on portrait, large tablet
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(expectedExpandedViewY);
- }
-
- /**
- * Calculates the Y position bubbles should be placed based on the config. Based on
- * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
- * {@link BubbleStackView.RelativeStackPosition}.
- */
- private float getDefaultYPosition() {
- final boolean isTablet = mPositioner.isLargeScreen();
-
- // On tablet the position is centered, on phone it is an offset from the top.
- final float desiredY = isTablet
- ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
- : mContext.getResources().getDimensionPixelOffset(
- R.dimen.bubble_stack_starting_offset_y);
- // Since we're visually centering the bubbles on tablet, use total screen height rather
- // than the available height.
- final float height = isTablet
- ? mPositioner.getScreenRect().height()
- : mPositioner.getAvailableRect().height();
- float offsetPercent = desiredY / height;
- offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
- final RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
- }
-
- /**
- * Sets up window manager to return config values based on what you need for the test.
- * By default it sets up a portrait phone without any insets.
- */
- private static class ConfigBuilder {
- private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
- private boolean mIsLargeScreen = false;
- private boolean mIsSmallTablet = false;
- private boolean mIsLandscape = false;
- private boolean mIsRtl = false;
- private Insets mInsets = Insets.of(0, 0, 0, 0);
-
- public ConfigBuilder setScreenBounds(Rect screenBounds) {
- mScreenBounds = screenBounds;
- return this;
- }
-
- public ConfigBuilder setLargeScreen() {
- mIsLargeScreen = true;
- return this;
- }
-
- public ConfigBuilder setSmallTablet() {
- mIsSmallTablet = true;
- return this;
- }
-
- public ConfigBuilder setLandscape() {
- mIsLandscape = true;
- return this;
- }
-
- public ConfigBuilder setRtl() {
- mIsRtl = true;
- return this;
- }
-
- public ConfigBuilder setInsets(Insets insets) {
- mInsets = insets;
- return this;
- }
-
- private DeviceConfig build() {
- return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl,
- mScreenBounds, mInsets);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index f58332198696..ae39fbcb4eed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -55,10 +56,9 @@ import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import java.util.concurrent.Executor
-/**
- * Tests for loading / inflating views & icons for a bubble.
- */
+/** Tests for loading / inflating views & icons for a bubble. */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -67,34 +67,47 @@ class BubbleViewInfoTest : ShellTestCase() {
private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
private lateinit var iconFactory: BubbleIconFactory
private lateinit var bubble: Bubble
-
private lateinit var bubbleController: BubbleController
private lateinit var mainExecutor: ShellExecutor
private lateinit var bubbleStackView: BubbleStackView
private lateinit var bubbleBarLayerView: BubbleBarLayerView
+ private lateinit var bubblePositioner: BubblePositioner
+ private lateinit var expandedViewManager: BubbleExpandedViewManager
+
+ private val bubbleTaskViewFactory = BubbleTaskViewFactory {
+ BubbleTaskView(mock<TaskView>(), mock<Executor>())
+ }
@Before
fun setup() {
metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
- iconFactory = BubbleIconFactory(context,
+ iconFactory =
+ BubbleIconFactory(
+ context,
60,
30,
Color.RED,
- mContext.resources.getDimensionPixelSize(
- R.dimen.importance_ring_stroke_width))
+ mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+ )
mainExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler,
- mainExecutor)
- val bubblePositioner = BubblePositioner(context, windowManager)
- val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
- BubbleEducationController(context), mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+ bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleData =
+ BubbleData(
+ context,
+ mock<BubbleLogger>(),
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor
+ )
val surfaceSynchronizer = { obj: Runnable -> obj.run() }
- bubbleController = BubbleController(
+ bubbleController =
+ BubbleController(
context,
shellInit,
shellCommandHandler,
@@ -122,18 +135,39 @@ class BubbleViewInfoTest : ShellTestCase() {
mock<Transitions>(),
mock<SyncTransactionQueue>(),
mock<IWindowManager>(),
- mock<BubbleProperties>())
+ mock<BubbleProperties>()
+ )
- bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ bubblePositioner,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleController,
+ mainExecutor
+ )
+ expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@Test
fun testPopulate() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
- bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populate(
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ bubblePositioner,
+ bubbleStackView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNotNull()
@@ -151,9 +185,17 @@ class BubbleViewInfoTest : ShellTestCase() {
@Test
fun testPopulateForBubbleBar() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ bubblePositioner,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNull()
@@ -176,12 +218,20 @@ class BubbleViewInfoTest : ShellTestCase() {
// exception here if the app has an issue loading the shortcut icon; we default to
// the app icon in that case / none of the icons will be null.
val mockIconFactory = mock<BubbleIconFactory>()
- whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
- any())).doThrow(RuntimeException())
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any()))
+ .doThrow(RuntimeException())
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- true /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ bubblePositioner,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ true /* skipInflation */
+ )
assertThat(info).isNotNull()
assertThat(info?.shortcutInfo).isNotNull()
@@ -194,8 +244,17 @@ class BubbleViewInfoTest : ShellTestCase() {
private fun createBubbleWithShortcut(): Bubble {
val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
- return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
- "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
- mainExecutor, metadataFlagListener)
+ return Bubble(
+ "mockKey",
+ shortcutInfo,
+ 1000,
+ Resources.ID_NULL,
+ "mockTitle",
+ 0 /* taskId */,
+ "mockLocus",
+ true /* isDismissible */,
+ mainExecutor,
+ metadataFlagListener
+ )
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index c1ff260836b8..60f1d271c3af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,52 +16,51 @@
package com.android.wm.shell.bubbles.animation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
- private int mDisplayWidth = 500;
- private int mDisplayHeight = 1000;
-
- private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
+ private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0);
+ private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release;
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
- private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
+ private final BubbleStackView.StackViewState mStackViewState =
+ new BubbleStackView.StackViewState();
- @SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
@@ -70,15 +69,13 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
- new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ new Rect(0, 0, 500, 1000));
BubbleStackView stackView = mock(BubbleStackView.class);
- when(stackView.getState()).thenReturn(getStackViewState());
mExpandedController = new ExpandedAnimationController(mPositioner,
mOnBubbleAnimatedOutAction,
stackView);
- spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -86,9 +83,18 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mExpansionPoint = new PointF(100, 100);
+
+ getStackViewState();
+ when(stackView.getState()).thenAnswer(i -> getStackViewState());
+ waitForMainThread();
}
- public BubbleStackView.StackViewState getStackViewState() {
+ @After
+ public void tearDown() {
+ waitForMainThread();
+ }
+
+ private BubbleStackView.StackViewState getStackViewState() {
mStackViewState.numberOfBubbles = mLayout.getChildCount();
mStackViewState.selectedIndex = 0;
mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
@@ -96,68 +102,71 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
}
@Test
- @Ignore
- public void testExpansionAndCollapse() throws InterruptedException {
- Runnable afterExpand = mock(Runnable.class);
- mExpandedController.expandFromStack(afterExpand);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
+ public void testExpansionAndCollapse() throws Exception {
+ expand();
testBubblesInCorrectExpandedPositions();
- verify(afterExpand).run();
+ waitForMainThread();
- Runnable afterCollapse = mock(Runnable.class);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterCollapse = semaphore::release;
mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- verify(afterExpand).run();
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForAnimation();
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y);
}
@Test
- @Ignore
- public void testOnChildAdded() throws InterruptedException {
+ public void testOnChildAdded() throws Exception {
expand();
+ waitForMainThread();
// Add another new view and wait for its animation.
final View newView = new FrameLayout(getContext());
mLayout.addView(newView, 0);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- @Ignore
- public void testOnChildRemoved() throws InterruptedException {
+ public void testOnChildRemoved() throws Exception {
expand();
+ waitForMainThread();
- // Remove some views and see if the remaining child views still pass the expansion test.
+ // Remove some views and verify the remaining child views still pass the expansion test.
mLayout.removeView(mViews.get(0));
mLayout.removeView(mViews.get(3));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice.
+ assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue();
+
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+ public void testDragBubbleOutDoesntNPE() {
mExpandedController.onGestureFinished();
mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
}
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
- mExpandedController.expandFromStack(mock(Runnable.class));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterExpand = semaphore::release;
+
+ mExpandedController.expandFromStack(afterExpand);
+ assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue();
}
/** Check that children are in the correct positions for being stacked. */
- private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ private void testStackedAtPosition(float x, float y) {
// Make sure the rest of the stack moved again, including the first bubble not moving, and
// is stacked to the right now that we're on the right side of the screen.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- assertEquals(x + i * offsetMultiplier * mStackOffset,
- mLayout.getChildAt(i).getTranslationX(), 2f);
- assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
+ assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f);
+ assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(),
+ 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
}
@@ -175,4 +184,22 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
+
+ private void waitForAnimation() throws Exception {
+ final Semaphore semaphore = new Semaphore(0);
+ boolean[] animating = new boolean[]{ true };
+ for (int i = 0; i < 4; i++) {
+ if (animating[0]) {
+ mMainThreadHandler.post(() -> {
+ if (!mExpandedController.isAnimating()) {
+ animating[0] = false;
+ semaphore.release();
+ }
+ });
+ Thread.sleep(500);
+ }
+ }
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 48ae2961b4be..2ed5addd900c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -164,11 +164,17 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase {
@Override
public void cancelAllAnimations() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::cancelAllAnimations);
}
@Override
public void cancelAnimationsOnView(View view) {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
}
@@ -221,6 +227,9 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase {
@Override
protected void startPathAnimation() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::startPathAnimation);
}
}
@@ -322,4 +331,9 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase {
e.printStackTrace();
}
}
+
+ /** Waits for the main thread to finish processing all pending runnables. */
+ public void waitForMainThread() {
+ runOnMainThreadAndBlock(() -> {});
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 01e2f988fbfc..2c0aa12f22d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -38,6 +38,7 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -51,6 +52,12 @@ import org.mockito.MockitoAnnotations;
import java.util.concurrent.Executor;
+/**
+ * Tests for the display IME controller.
+ *
+ * <p> Build/Install/Run:
+ * atest WMShellUnitTests:DisplayImeControllerTest
+ */
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
@@ -99,13 +106,13 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
+ mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
verifyZeroInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
+ mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
verifyZeroInteractions(mExecutor);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 956f1cd419c2..669e433ba386 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -50,6 +50,12 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
+/**
+ * Tests for the display insets controller.
+ *
+ * <p> Build/Install/Run:
+ * atest WMShellUnitTests:DisplayInsetsControllerTest
+ */
@SmallTest
public class DisplayInsetsControllerTest extends ShellTestCase {
@@ -114,9 +120,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
- null /* statsToken */);
+ ImeTracker.Token.empty());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
- null /* statsToken */);
+ ImeTracker.Token.empty());
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -136,9 +142,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
- null /* statsToken */);
+ ImeTracker.Token.empty());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
- null /* statsToken */);
+ ImeTracker.Token.empty());
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
new file mode 100644
index 000000000000..2f5fe11634a4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.wm.shell.ShellTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class MultiInstanceHelperTest : ShellTestCase() {
+
+ @Before
+ fun setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext))
+ }
+
+ @Test
+ fun getShortcutComponent_nullShortcuts() {
+ val launcherApps = mock<LauncherApps>()
+ whenever(launcherApps.getShortcuts(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(null)
+ assertEquals(null, MultiInstanceHelper.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_noShortcuts() {
+ val launcherApps = mock<LauncherApps>()
+ whenever(launcherApps.getShortcuts(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(ArrayList<ShortcutInfo>())
+ assertEquals(null, MultiInstanceHelper.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_validShortcut() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build()
+ val launcherApps = mock<LauncherApps>()
+ whenever(launcherApps.getShortcuts(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(arrayListOf(shortcutInfo))
+ assertEquals(component, MultiInstanceHelper.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun supportsMultiInstanceSplit_inStaticAllowList() {
+ val allowList = arrayOf(TEST_PACKAGE)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList)
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ }
+
+ @Test
+ fun supportsMultiInstanceSplit_notInStaticAllowList() {
+ val allowList = arrayOf(TEST_PACKAGE)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList)
+ val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY)
+ assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ }
+
+ @Test
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun supportsMultiInstanceSplit_activityPropertyTrue() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val pm = mock<PackageManager>()
+ val activityProp = PackageManager.Property("", true, "", "")
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component)))
+ .thenReturn(activityProp)
+ val appProp = PackageManager.Property("", false, "", "")
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName)))
+ .thenReturn(appProp)
+
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray())
+ // Expect activity property to override application property
+ assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ }
+
+ @Test
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val pm = mock<PackageManager>()
+ val activityProp = PackageManager.Property("", false, "", "")
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component)))
+ .thenReturn(activityProp)
+ val appProp = PackageManager.Property("", true, "", "")
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName)))
+ .thenReturn(appProp)
+
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray())
+ // Expect activity property to override application property
+ assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ }
+
+ @Test
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val pm = mock<PackageManager>()
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component)))
+ .thenThrow(PackageManager.NameNotFoundException())
+ val appProp = PackageManager.Property("", true, "", "")
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName)))
+ .thenReturn(appProp)
+
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray())
+ // Expect fall through to app property
+ assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ }
+
+ @Test
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun supportsMultiInstanceSplit_noActivityOrAppProperty() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val pm = mock<PackageManager>()
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component)))
+ .thenThrow(PackageManager.NameNotFoundException())
+ whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName)))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray())
+ assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ }
+
+ companion object {
+ val TEST_PACKAGE = "com.android.wm.shell.common"
+ val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake";
+ val TEST_ACTIVITY = "TestActivity";
+ val TEST_SHORTCUT_ID = "test_shortcut_1"
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index 9f1ee6c92700..a4fb3504f31d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -30,6 +30,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mockito
import org.mockito.Mockito.`when`
@@ -97,7 +98,7 @@ class MagnetizedObjectTest : ShellTestCase() {
// The mock target view will pretend that it's 200x200, and at (400, 800). This means it's
// occupying the bounds (400, 800, 600, 1000) and it has a center of (500, 900).
- `when`(targetView.width).thenReturn(targetSize) // width = 200
+ `when`(targetView.width).thenReturn(targetSize) // width = 200
`when`(targetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
(invocation.arguments[0] as IntArray).also { location ->
@@ -200,9 +201,11 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(x = 200, y = 200))
// You can't become unstuck if you were never stuck in the first place.
- verify(magnetListener, never()).onStuckToTarget(magneticTarget)
+ verify(magnetListener, never()).onStuckToTarget(magneticTarget,
+ magnetizedObject)
verify(magnetListener, never()).onUnstuckFromTarget(
- eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(magneticTarget), eq(magnetizedObject),
+ ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
eq(false))
// Move into and then around inside the magnetic field.
@@ -212,9 +215,10 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(x = targetCenterX + 100, y = targetCenterY + 100))
// We should only have received one call to onStuckToTarget and none to unstuck.
- verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget, magnetizedObject)
verify(magnetListener, never()).onUnstuckFromTarget(
- eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(magneticTarget), eq(magnetizedObject),
+ ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
eq(false))
// Move out of the field and then release.
@@ -225,7 +229,8 @@ class MagnetizedObjectTest : ShellTestCase() {
// We should have received one unstuck call and no more stuck calls. We also should never
// have received an onReleasedInTarget call.
verify(magnetListener, times(1)).onUnstuckFromTarget(
- eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(magneticTarget), eq(magnetizedObject),
+ ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
eq(false))
verifyNoMoreInteractions(magnetListener)
}
@@ -241,8 +246,8 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(
x = targetCenterX, y = targetCenterY))
- verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
- verify(magnetListener, never()).onReleasedInTarget(magneticTarget)
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget, magnetizedObject)
+ verify(magnetListener, never()).onReleasedInTarget(magneticTarget, magnetizedObject)
// Move back out.
dispatchMotionEvents(
@@ -251,9 +256,11 @@ class MagnetizedObjectTest : ShellTestCase() {
y = targetCenterY - magneticFieldRadius))
verify(magnetListener, times(1)).onUnstuckFromTarget(
- eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(magneticTarget),
+ eq(magnetizedObject),
+ ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
eq(false))
- verify(magnetListener, never()).onReleasedInTarget(magneticTarget)
+ verify(magnetListener, never()).onReleasedInTarget(magneticTarget, magnetizedObject)
// Move in again and release in the magnetic field.
dispatchMotionEvents(
@@ -263,8 +270,8 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(
x = targetCenterX, y = targetCenterY, action = MotionEvent.ACTION_UP))
- verify(magnetListener, times(2)).onStuckToTarget(magneticTarget)
- verify(magnetListener).onReleasedInTarget(magneticTarget)
+ verify(magnetListener, times(2)).onStuckToTarget(magneticTarget, magnetizedObject)
+ verify(magnetListener).onReleasedInTarget(magneticTarget, magnetizedObject)
verifyNoMoreInteractions(magnetListener)
}
@@ -275,11 +282,11 @@ class MagnetizedObjectTest : ShellTestCase() {
// Forcefully fling the object towards the target (but never touch the magnetic field).
dispatchMotionEvents(
getMotionEvent(
- x = targetCenterX,
+ x = 0,
y = 0,
action = MotionEvent.ACTION_DOWN),
getMotionEvent(
- x = targetCenterX,
+ x = targetCenterX / 2,
y = targetCenterY / 2),
getMotionEvent(
x = targetCenterX,
@@ -287,7 +294,7 @@ class MagnetizedObjectTest : ShellTestCase() {
action = MotionEvent.ACTION_UP))
// Nevertheless it should have ended up stuck to the target.
- verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget, magnetizedObject)
}
@Test
@@ -365,7 +372,7 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(x = 100, y = 900))
// Verify that we received an onStuck for the second target, and no others.
- verify(magnetListener).onStuckToTarget(secondMagneticTarget)
+ verify(magnetListener).onStuckToTarget(secondMagneticTarget, magnetizedObject)
verifyNoMoreInteractions(magnetListener)
// Drag into the original target.
@@ -375,8 +382,9 @@ class MagnetizedObjectTest : ShellTestCase() {
// We should have unstuck from the second one and stuck into the original one.
verify(magnetListener).onUnstuckFromTarget(
- eq(secondMagneticTarget), anyFloat(), anyFloat(), eq(false))
- verify(magnetListener).onStuckToTarget(magneticTarget)
+ eq(secondMagneticTarget), eq(magnetizedObject),
+ anyFloat(), anyFloat(), eq(false))
+ verify(magnetListener).onStuckToTarget(magneticTarget, magnetizedObject)
verifyNoMoreInteractions(magnetListener)
}
@@ -393,7 +401,7 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(x = 100, y = 650, action = MotionEvent.ACTION_UP))
// Verify that we received an onStuck for the second target.
- verify(magnetListener).onStuckToTarget(secondMagneticTarget)
+ verify(magnetListener).onStuckToTarget(secondMagneticTarget, magnetizedObject)
// Fling towards the first target.
dispatchMotionEvents(
@@ -402,18 +410,82 @@ class MagnetizedObjectTest : ShellTestCase() {
getMotionEvent(x = 500, y = 650, action = MotionEvent.ACTION_UP))
// Verify that we received onStuck for the original target.
- verify(magnetListener).onStuckToTarget(magneticTarget)
+ verify(magnetListener).onStuckToTarget(magneticTarget, magnetizedObject)
+ }
+
+ @Test
+ fun testMagneticTargetHasScreenOffset_moveIntoAndReleaseInTarget() {
+ magneticTarget.screenVerticalOffset = 500
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+ // Moved into the target location, but it should be shifted due to screen offset.
+ // Should not get stuck.
+ verify(magnetListener, never()).onStuckToTarget(magneticTarget, magnetizedObject)
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY + 500))
+ verify(magnetListener).onStuckToTarget(magneticTarget, magnetizedObject)
+
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY + 500,
+ action = MotionEvent.ACTION_UP
+ )
+ )
+
+ verify(magnetListener).onReleasedInTarget(magneticTarget, magnetizedObject)
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testMagneticTargetHasScreenOffset_screenOffsetUpdates() {
+ magneticTarget.screenVerticalOffset = 500
+ val adjustedTargetCenter = targetCenterY + 500
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = adjustedTargetCenter))
+ dispatchMotionEvents(getMotionEvent(x = 0, y = 0))
+ verify(magnetListener).onStuckToTarget(magneticTarget, magnetizedObject)
+ verify(magnetListener)
+ .onUnstuckFromTarget(eq(magneticTarget), eq(magnetizedObject),
+ anyFloat(), anyFloat(), anyBoolean())
+
+ // Offset if removed, we should now get stuck at the target location
+ magneticTarget.screenVerticalOffset = 0
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+ verify(magnetListener, times(2)).onStuckToTarget(magneticTarget, magnetizedObject)
+ }
+
+ @Test
+ fun testMagneticTargetHasScreenOffset_flingTowardsTarget() {
+ timeStep = 10
+
+ magneticTarget.screenVerticalOffset = 500
+ val adjustedTargetCenter = targetCenterY + 500
+
+ // Forcefully fling the object towards the target (but never touch the magnetic field).
+ dispatchMotionEvents(
+ getMotionEvent(x = 0, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = targetCenterX / 2, y = adjustedTargetCenter / 2),
+ getMotionEvent(
+ x = targetCenterX,
+ y = adjustedTargetCenter - magneticFieldRadius * 2,
+ action = MotionEvent.ACTION_UP
+ )
+ )
+
+ // Nevertheless it should have ended up stuck to the target.
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget, magnetizedObject)
}
private fun getSecondMagneticTarget(): MagnetizedObject.MagneticTarget {
// The first target view is at bounds (400, 800, 600, 1000) and it has a center of
// (500, 900). We'll add a second one at bounds (0, 800, 200, 1000) with center (100, 900).
val secondTargetView = mock(View::class.java)
- var secondTargetCenterX = 100
- var secondTargetCenterY = 900
+ val secondTargetCenterX = 100
+ val secondTargetCenterY = 900
`when`(secondTargetView.context).thenReturn(context)
- `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
+ `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
`when`(secondTargetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
(invocation.arguments[0] as Runnable).run()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 23a4e3956289..dd358e757fde 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -83,6 +84,7 @@ public class CompatUILayoutTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -221,6 +223,9 @@ public class CompatUILayoutTest extends ShellTestCase {
taskInfo.taskId = TASK_ID;
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed55192..4f261cd79d39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,7 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +28,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +41,7 @@ import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -50,6 +53,7 @@ import android.view.View;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +63,7 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,8 +81,12 @@ import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIWindowManagerTest extends ShellTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
+ private static final int TASK_WIDTH = 2000;
+ private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
@@ -93,6 +102,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -138,6 +148,13 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.mHasSizeCompat = false;
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+ // Returns false and doesn't create layout if restart button should be hidden.
+ clearInvocations(mWindowManager);
+ mWindowManager.mHasSizeCompat = true;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
verify(mWindowManager, never()).inflateLayout();
}
@@ -199,6 +216,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// No diff
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
@@ -283,7 +301,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
- mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -303,6 +320,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager).inflateLayout();
+
+ // Change shouldShowSizeCompatRestartButton to false and pass canShow true, layout
+ // shouldn't be inflated
+ clearInvocations(mWindowManager);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
}
@Test
@@ -464,6 +490,36 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ public void testShouldShowSizeCompatRestartButton() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+ doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
+
+ // Simulate rotation of activity in square display
+ TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate exiting split screen/folding
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate folding
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+ }
+
private static TaskInfo createTaskInfo(boolean hasSizeCompat,
@AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
@@ -471,6 +527,11 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ // Letterboxed activity that takes half the screen should show size compat restart button
+ taskInfo.configuration.windowConfiguration.setBounds(
+ new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 9fe2cb11e804..81ba4b37d13b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -113,8 +113,22 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
mExecutor = new TestShellExecutor();
mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ final int displayWidth = 1000;
+ final int displayHeight = 1200;
+ displayInfo.logicalWidth = displayWidth;
+ displayInfo.logicalHeight = displayHeight;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, displayWidth, displayHeight));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, displayHeight - 200, displayWidth, displayHeight);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
- mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mSyncTransactionQueue, mTaskListener, displayLayout, new CompatUIHintsState(),
mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
mUserAspectRatioButtonShownChecker, s -> {});
spyOn(mWindowManager);
@@ -253,6 +267,31 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+
+ final Rect stableBounds = mWindowManager.getTaskStableBounds();
+ final int stableHeight = stableBounds.height();
+
+ // Letterboxed activity bounds equal to stable bounds, layout shouldn't be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width();
+
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Letterboxed activity bounds smaller than stable bounds, layout should be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight - 100;
+
+ clearInvocations(mWindowManager);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
new file mode 100644
index 000000000000..4548fcb06c55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.util.FrameworkStatsLog
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.eq
+
+/**
+ * Tests for [DesktopModeEventLogger].
+ */
+class DesktopModeEventLoggerTest {
+
+ private val desktopModeEventLogger = DesktopModeEventLogger()
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java).build()!!
+
+ @Test
+ fun logSessionEnter_enterReason() = runBlocking {
+ desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ /* event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
+ /* enter_reason */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER),
+ /* exit_reason */
+ eq(0),
+ /* sessionId */
+ eq(SESSION_ID)
+ )
+ }
+ }
+
+ @Test
+ fun logSessionExit_exitReason() = runBlocking {
+ desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ /* event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT),
+ /* enter_reason */
+ eq(0),
+ /* exit_reason */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT),
+ /* sessionId */
+ eq(SESSION_ID)
+ )
+ }
+ }
+
+ @Test
+ fun logTaskAdded_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ @Test
+ fun logTaskRemoved_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ @Test
+ fun logTaskInfoChanged_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ companion object {
+ private const val SESSION_ID = 1
+ private const val TASK_ID = 1
+ private const val TASK_UID = 1
+ private const val TASK_X = 0
+ private const val TASK_Y = 0
+ private const val TASK_HEIGHT = 100
+ private const val TASK_WIDTH = 100
+
+ private val TASK_UPDATE = TaskUpdate(
+ TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
+ )
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3fe78efdf2b1..445f74a52b0d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -124,7 +124,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@@ -148,7 +148,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -162,8 +162,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
@@ -175,16 +175,16 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
executor.flushAll()
// Listener for secondary display is notified
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
// No changes to listener for default display
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
@@ -198,7 +198,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
// Mark task 1 visible on secondary display
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
@@ -208,11 +208,11 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
// 1 - visible task added
// 2 - visible task removed
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// Secondary display should have 1 call for visible task added
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@Test
@@ -224,17 +224,17 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
}
@Test
@@ -397,8 +397,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
- var hasVisibleTasksOnDefaultDisplay = false
- var hasVisibleTasksOnSecondaryDisplay = false
+ var visibleTasksCountOnDefaultDisplay = 0
+ var visibleTasksCountOnSecondaryDisplay = 0
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
@@ -409,14 +409,14 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
var stashedChangesOnDefaultDisplay = 0
var stashedChangesOnSecondaryDisplay = 0
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
- hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnDefaultDisplay = visibleTasksCount
visibleChangesOnDefaultDisplay++
}
SECOND_DISPLAY -> {
- hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnSecondaryDisplay = visibleTasksCount
visibleChangesOnSecondaryDisplay++
}
else -> fail("Visible task listener received unexpected display id: $displayId")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
new file mode 100644
index 000000000000..9703dce8bf53
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.Rect
+import android.graphics.Region
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeVisualIndicatorTest : ShellTestCase() {
+ @Mock private lateinit var taskInfo: RunningTaskInfo
+ @Mock private lateinit var syncQueue: SyncTransactionQueue
+ @Mock private lateinit var displayController: DisplayController
+ @Mock private lateinit var taskSurface: SurfaceControl
+ @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var displayLayout: DisplayLayout
+
+ private lateinit var visualIndicator: DesktopModeVisualIndicator
+
+ @Before
+ fun setUp() {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
+ context, taskSurface, taskDisplayAreaOrganizer)
+ whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
+ whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+ whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ }
+
+ @Test
+ fun testFullscreenRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_from_desktop_width
+ )
+ var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(
+ DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
+ -50,
+ DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
+ 2 * STABLE_INSETS.top))
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ }
+
+ @Test
+ fun testSplitLeftRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_split_from_desktop_height)
+ var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+ testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
+ testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+ }
+
+ @Test
+ fun testSplitRightRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_split_from_desktop_height)
+ var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+ testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
+ testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+ }
+
+ @Test
+ fun testToDesktopRegionCalculation() {
+ val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+ val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion)
+ var testRegion = Region()
+ testRegion.union(DISPLAY_BOUNDS)
+ testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
+ testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
+ testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
+ assertThat(desktopRegion).isEqualTo(testRegion)
+ }
+
+ companion object {
+ private const val TRANSITION_AREA_WIDTH = 32
+ private const val CAPTION_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private const val NAVBAR_HEIGHT = 50
+ private val STABLE_INSETS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 94c862bd7a4f..35c803b78674 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -48,12 +48,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -106,6 +108,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
+ @Mock lateinit var dragAndDropController: DragAndDropController
+ @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -148,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shellTaskOrganizer,
syncQueue,
rootTaskDisplayAreaOrganizer,
+ dragAndDropController,
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
@@ -156,6 +161,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository,
launchAdjacentController,
recentsTransitionHandler,
+ multiInstanceHelper,
shellExecutor
)
}
@@ -303,7 +309,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -313,7 +319,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -321,7 +327,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(desktopModeWindowDecoration, 999)
+ controller.moveToDesktop(999)
verifyWCTNotExecuted()
}
@@ -332,7 +338,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
+ controller.moveToDesktop(fullscreenTask)
with(getLatestMoveToDesktopWct()) {
// Operations should include home task, freeform task
@@ -354,7 +360,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
+ controller.moveToDesktop(fullscreenTaskDefault)
with(getLatestMoveToDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
@@ -368,24 +374,24 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToDesktop_splitTaskExitsSplit() {
val task = setUpSplitScreenTask()
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
- eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
)
}
@Test
fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
val task = setUpFullscreenTask()
- controller.moveToDesktop(desktopModeWindowDecoration, task)
+ controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
- eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
)
}
@@ -393,7 +399,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -403,7 +409,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
@@ -411,7 +417,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
- controller.moveToFullscreen(999, desktopModeWindowDecoration)
+ controller.moveToFullscreen(999)
verifyWCTNotExecuted()
}
@@ -420,7 +426,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId)
with(getLatestExitDesktopWct()) {
assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
@@ -734,6 +740,63 @@ class DesktopTasksControllerTest : ShellTestCase() {
shellExecutor.flushAll()
verify(launchAdjacentController).launchAdjacentEnabled = true
}
+ @Test
+ fun enterDesktop_fullscreenTaskIsMovedToDesktop() {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun enterDesktop_splitScreenTaskIsMovedToDesktop() {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
+
+ task4.parentTaskId = task1.taskId
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
+ )
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterFullscreen(DEFAULT_DISPLAY)
+
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
@@ -802,7 +865,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture())
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 3bc90ade898e..98e90d60b3b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -24,7 +24,6 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_D
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
-import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import org.junit.Before
import org.junit.Test
@@ -34,9 +33,11 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
+import java.util.function.Supplier
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@@ -150,6 +151,23 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
+ fun startDragToDesktop_anotherTransitionInProgress_startDropped() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+
+ // Simulate attempt to start two drag to desktop transitions.
+ startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(task, dragAnimator)
+
+ // Verify transition only started once.
+ verify(transitions, times(1)).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
fun cancelDragToDesktop_startWasReady_cancel() {
val task = createTask()
val dragAnimator = mock<MoveToDesktopAnimator>()
@@ -189,6 +207,32 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
verifyZeroInteractions(dragAnimator)
}
+ @Test
+ fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
+ // Then cancel is called before the transition was started.
+ handler.cancelDragToDesktopTransition()
+
+ // Verify cancel is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
+ fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
+ // Then finish is called before the transition was started.
+ handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+ // Verify finish is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
private fun startDragToDesktopTransition(
task: RunningTaskInfo,
dragAnimator: MoveToDesktopAnimator
@@ -202,7 +246,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator, mock())
+ handler.startDragToDesktopTransition(task.taskId, dragAnimator)
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 54f36f61859d..a64ebd301c00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -45,12 +45,14 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
+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.ShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -84,7 +86,9 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellExecutor mMainExecutor;
@Mock
- private WindowManager mWindowManager;
+ private Transitions mTransitions;
+ @Mock
+ private GlobalDragListener mGlobalDragListener;
private DragAndDropController mController;
@@ -93,7 +97,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
- mMainExecutor);
+ mGlobalDragListener, mTransitions, mMainExecutor);
mController.onInit();
}
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 1b347e01888e..5dd9d8a859d6 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
@@ -22,9 +22,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
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;
@@ -46,6 +48,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -61,6 +65,7 @@ import android.content.res.Resources;
import android.graphics.Insets;
import android.os.RemoteException;
import android.view.DisplayInfo;
+import android.view.DragEvent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -70,12 +75,15 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.startingsurface.TaskSnapshotWindow;
+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;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.Collections;
@@ -107,6 +115,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
private DragAndDropPolicy mPolicy;
private ClipData mActivityClipData;
+ private ClipData mLaunchableIntentClipData;
private ClipData mNonResizeableActivityClipData;
private ClipData mTaskClipData;
private ClipData mShortcutClipData;
@@ -115,9 +124,16 @@ public class DragAndDropPolicyTest extends ShellTestCase {
private ActivityManager.RunningTaskInfo mFullscreenAppTask;
private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
+ private MockitoSession mMockitoSession;
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .strictness(LENIENT)
+ .mockStatic(DragUtils.class)
+ .startMocking();
+ when(DragUtils.canHandleDrag(any())).thenReturn(true);
Resources res = mock(Resources.class);
Configuration config = new Configuration();
@@ -134,11 +150,12 @@ public class DragAndDropPolicyTest extends ShellTestCase {
mInsets = Insets.of(0, 0, 0, 0);
mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
- mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
- mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
+ mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
+ mLaunchableIntentClipData = createIntentClipData();
+ mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
- mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK);
- mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT);
+ mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK);
+ mShortcutClipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -149,10 +166,15 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
}
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
/**
- * Creates a clip data that is by default resizeable.
+ * Creates an app-based clip data that is by default resizeable.
*/
- private ClipData createClipData(String mimeType) {
+ private ClipData createAppClipData(String mimeType) {
ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType });
Intent i = new Intent();
switch (mimeType) {
@@ -164,7 +186,9 @@ public class DragAndDropPolicyTest extends ShellTestCase {
i.putExtra(Intent.EXTRA_TASK_ID, 12345);
break;
case MIMETYPE_APPLICATION_ACTIVITY:
- i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class));
+ final PendingIntent pi = mock(PendingIntent.class);
+ doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle();
+ i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi);
break;
}
i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
@@ -175,6 +199,22 @@ public class DragAndDropPolicyTest extends ShellTestCase {
return data;
}
+ /**
+ * Creates an intent-based clip data that is by default resizeable.
+ */
+ private ClipData createIntentClipData() {
+ ClipDescription clipDescription = new ClipDescription("Intent",
+ new String[] { MIMETYPE_TEXT_INTENT });
+ PendingIntent intent = mock(PendingIntent.class);
+ when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle());
+ ClipData.Item item = new ClipData.Item.Builder()
+ .setIntentSender(intent.getIntentSender())
+ .build();
+ ClipData data = new ClipData(clipDescription, item);
+ when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent);
+ return data;
+ }
+
private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) {
ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.configuration.windowConfiguration.setActivityType(actType);
@@ -204,58 +244,85 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+ dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);
+ }
+
+ @Test
+ public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ dragOverFullscreenApp_expectSplitScreenTargets(mActivityClipData);
+ }
+
+ @Test
+ public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mActivityClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() {
+ dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() {
+ dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData);
+ }
+
+ private void dragOverFullscreenHome_expectOnlyFullscreenTarget(ClipData data) {
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mLandscapeDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mLandscapeDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_UNDEFINED), any());
}
- @Test
- public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mLandscapeDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mLandscapeDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
- @Test
- public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mPortraitDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mPortraitDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -263,7 +330,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
new file mode 100644
index 000000000000..e731b06c0947
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.draganddrop
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.RemoteException
+import android.view.DragEvent
+import android.view.DragEvent.ACTION_DROP
+import android.view.IWindowManager
+import android.view.SurfaceControl
+import android.window.IUnhandledDragCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.draganddrop.GlobalDragListener.GlobalDragListenerCallback
+import java.util.function.Consumer
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for the unhandled drag controller.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UnhandledDragControllerTest : ShellTestCase() {
+ private val mIWindowManager = mock<IWindowManager>()
+ private val mMainExecutor = mock<ShellExecutor>()
+
+ private lateinit var mController: GlobalDragListener
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ mController = GlobalDragListener(mIWindowManager, mMainExecutor)
+ }
+
+ @Test
+ fun setListener_registersUnregistersWithWM() {
+ mController.setListener(object : GlobalDragListenerCallback {})
+ mController.setListener(object : GlobalDragListenerCallback {})
+ mController.setListener(object : GlobalDragListenerCallback {})
+ verify(mIWindowManager, Mockito.times(1))
+ .setGlobalDragListener(ArgumentMatchers.any())
+
+ reset(mIWindowManager)
+ mController.setListener(null)
+ mController.setListener(null)
+ mController.setListener(null)
+ verify(mIWindowManager, Mockito.times(1))
+ .setGlobalDragListener(ArgumentMatchers.isNull())
+ }
+
+ @Test
+ fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
+ // Simulate an unhandled drop
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ null, null, false)
+ val wmCallback = mock<IUnhandledDragCallback>()
+ mController.onUnhandledDrop(dropEvent, wmCallback)
+
+ verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
+ }
+
+ @Test
+ fun onUnhandledDrop_withListener_expectNotifyHandled() {
+ val lastDragEvent = arrayOfNulls<DragEvent>(1)
+
+ // Set a listener to listen for unhandled drops
+ mController.setListener(object : GlobalDragListenerCallback {
+ override fun onUnhandledDrop(dragEvent: DragEvent,
+ onFinishedCallback: Consumer<Boolean>) {
+ lastDragEvent[0] = dragEvent
+ onFinishedCallback.accept(true)
+ dragEvent.dragSurface.release()
+ }
+ })
+
+ // Simulate an unhandled drop
+ val dragSurface = mock<SurfaceControl>()
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ dragSurface, null, false)
+ val wmCallback = mock<IUnhandledDragCallback>()
+ mController.onUnhandledDrop(dropEvent, wmCallback)
+
+ verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
+ verify(dragSurface).release()
+ assertEquals(lastDragEvent.get(0), dropEvent)
+ }
+
+ @Test
+ fun onCrossWindowDrop() {
+ val lastTaskInfo = arrayOfNulls<RunningTaskInfo>(1)
+
+ // Set a listener to listen for unhandled drops
+ mController.setListener(object : GlobalDragListenerCallback {
+ override fun onCrossWindowDrop(taskInfo: RunningTaskInfo) {
+ lastTaskInfo[0] = taskInfo
+ }
+ })
+
+ // Simulate a cross-window drop
+ val taskInfo = mock<RunningTaskInfo>()
+ mController.onCrossWindowDrop(taskInfo)
+ assertEquals(lastTaskInfo.get(0), taskInfo)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
new file mode 100644
index 000000000000..71eea4bb59b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -0,0 +1,110 @@
+/*
+ * 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.freeform;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link FreeformTaskListener}
+ * Build/Install/Run:
+ * atest WMShellUnitTests:FreeformTaskListenerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FreeformTaskListenerTests extends ShellTestCase {
+
+ @Mock
+ private ShellTaskOrganizer mTaskOrganizer;
+ @Mock
+ private ShellInit mShellInit;
+ @Mock
+ private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private FreeformTaskListener mFreeformTaskListener;
+ private StaticMockitoSession mMockitoSession;
+
+ @Before
+ public void setup() {
+ mMockitoSession = mockitoSession().initMocks(this)
+ .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
+ mFreeformTaskListener = new FreeformTaskListener(
+ mShellInit,
+ mTaskOrganizer,
+ Optional.of(mDesktopModeTaskRepository),
+ mWindowDecorViewModel);
+ }
+
+ @Test
+ public void testFocusTaskChanged_freeformTaskIsAddedToRepo() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isFocused = true;
+
+ mFreeformTaskListener.onFocusTaskChanged(task);
+
+ verify(mDesktopModeTaskRepository).addOrMoveFreeformTaskToTop(task.taskId);
+ }
+
+ @Test
+ public void testFocusTaskChanged_fullscreenTaskIsNotAddedToRepo() {
+ ActivityManager.RunningTaskInfo fullscreenTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ fullscreenTask.isFocused = true;
+
+ mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
+
+ verify(mDesktopModeTaskRepository, never())
+ .addOrMoveFreeformTaskToTop(fullscreenTask.taskId);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 46259a8b177f..080b0ae006ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -354,14 +354,17 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
}
@Test
- public void getEntryDestinationBounds_reentryStateExists_restoreLastSize() {
+ public void getEntryDestinationBounds_reentryStateExists_restoreProportionalSize() {
mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
+ final Size maxSize = mSizeSpecSource.getMaxSize(DEFAULT_ASPECT_RATIO);
+ mPipBoundsState.setMaxSize(maxSize.getWidth(), maxSize.getHeight());
final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
reentryBounds.scale(1.25f);
+ mPipBoundsState.setBounds(reentryBounds); // this updates the bounds scale used in reentry
+
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(
- new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+ mPipBoundsState.saveReentryState(reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
assertEquals(reentryBounds.width(), destinationBounds.width());
@@ -375,8 +378,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
reentryBounds.offset(0, -100);
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(
- new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+ mPipBoundsState.saveReentryState(reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index db98abbbcbf1..304da75f870c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -114,22 +114,19 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetReentryState() {
- final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(size, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
- assertEquals(size, state.getSize());
assertEquals(snapFraction, state.getSnapFraction(), 0.01);
}
@Test
public void testClearReentryState() {
- final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(size, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
mPipBoundsState.clearReentryState();
assertNull(mPipBoundsState.getReentryState());
@@ -138,20 +135,19 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
assertNotNull(state);
- assertEquals(DEFAULT_SIZE, state.getSize());
assertEquals(DEFAULT_SNAP_FRACTION, state.getSnapFraction(), 0.01);
}
@Test
public void testSetLastPipComponentName_changed_clearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 800f9e4e5371..e74c804d4f40 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -36,7 +36,6 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Rational;
@@ -45,6 +44,8 @@ import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
+import androidx.test.filters.SmallTest;
+
import com.android.wm.shell.MockSurfaceControlHelper;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -118,7 +119,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
- mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
+ mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
+ Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
mMainExecutor.flushAll();
preparePipTaskOrg();
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 4eb519334e12..3384509f1da9 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
@@ -42,10 +42,10 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Size;
+
+import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -256,40 +256,13 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
- public void saveReentryState_noUserResize_doesNotSaveSize() {
- final Rect bounds = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(false);
-
- mPipController.saveReentryState(bounds);
-
- verify(mMockPipBoundsState).saveReentryState(null, 1.0f);
- }
-
- @Test
- public void saveReentryState_nonEmptyUserResizeBounds_savesSize() {
- final Rect bounds = new Rect(0, 0, 10, 10);
- final Rect resizedBounds = new Rect(0, 0, 30, 30);
- when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
-
- mPipController.saveReentryState(bounds);
-
- verify(mMockPipBoundsState).saveReentryState(new Size(30, 30), 1.0f);
- }
-
- @Test
- public void saveReentryState_emptyUserResizeBounds_savesSize() {
+ public void saveReentryState_savesPipBoundsState() {
final Rect bounds = new Rect(0, 0, 10, 10);
- final Rect resizedBounds = new Rect(0, 0, 0, 0);
when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
mPipController.saveReentryState(bounds);
- verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f);
+ verify(mMockPipBoundsState).saveReentryState(1.0f);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 0f8db85dcef4..b583acda1c9a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -16,10 +16,10 @@
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -30,6 +30,7 @@ import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import org.junit.Assert;
import org.junit.Before;
@@ -38,7 +39,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
/**
- * Unit test against {@link PipDoubleTapHelper}.
+ * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}.
*/
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 9719ba89b4bb..ace09a82d71c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -55,6 +55,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Unit tests against {@link PipResizeGestureHandler}
*/
@@ -114,15 +116,16 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mSizeSpecSource);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
- mMockPipTransitionController, mFloatingContentCoordinator);
+ mMockPipTransitionController, mFloatingContentCoordinator,
+ Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchState = new PipTouchState(ViewConfiguration.get(mContext),
() -> {}, () -> {}, mMainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
mPipDismissTargetHandler,
- (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
- mMainExecutor) {
+ () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+ mMainExecutor, null /* pipPerfHintController */) {
@Override
public void pilferPointers() {
// Overridden just to avoid calling into InputMonitor.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 9aaabd130527..92762fa68550 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -51,6 +51,8 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Unit tests against {@link PipTouchHandler}, including but not limited to:
* - Update movement bounds based on new bounds
@@ -116,10 +118,12 @@ public class PipTouchHandlerTest extends ShellTestCase {
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
- mMockPipTransitionController, mFloatingContentCoordinator);
+ mMockPipTransitionController, mFloatingContentCoordinator,
+ Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer,
- pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor,
+ Optional.empty() /* pipPerfHintControllerOptional */);
// We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index 45f6c8c7f69f..72db6e091307 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -23,20 +23,21 @@ import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
-import static java.util.Collections.EMPTY_LIST;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static java.util.Collections.EMPTY_LIST;
+
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.graphics.drawable.Icon;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipMediaController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 855b7ee04702..315d97ed333b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -63,6 +63,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -84,6 +85,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for {@link SplitScreenController}
*/
@@ -109,6 +112,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock LaunchAdjacentController mLaunchAdjacentController;
@Mock WindowDecorViewModel mWindowDecorViewModel;
@Mock DesktopTasksController mDesktopTasksController;
+ @Mock MultiInstanceHelper mMultiInstanceHelper;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -124,8 +128,9 @@ public class SplitScreenControllerTests extends ShellTestCase {
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
- mDesktopTasksController, mMainExecutor, mStageCoordinator));
+ mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
+ Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
+ mStageCoordinator, mMultiInstanceHelper, mMainExecutor));
}
@Test
@@ -200,7 +205,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -237,12 +242,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -259,14 +265,14 @@ public class SplitScreenControllerTests extends ShellTestCase {
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
@Test
public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
- doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -283,6 +289,15 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mStageCoordinator).switchSplitPosition(anyString());
}
+ @Test
+ public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
+ final String reason = "test";
+ when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
+ mSplitScreenController.switchSplitPosition(reason);
+ mSplitScreenController.switchSplitPosition(reason);
+ verify(mStageCoordinator, times(1)).switchSplitPosition(reason);
+ }
+
private Intent createStartIntent(String activityName) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 050443914355..fbc0db9c2850 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.IBinder;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
@@ -40,6 +39,8 @@ import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.test.filters.SmallTest;
+
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e22bf3de30e4..e9da25813510 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
@@ -420,6 +421,30 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void testTransitionFilterActivityComponent() {
+ TransitionFilter filter = new TransitionFilter();
+ ComponentName cmpt = new ComponentName("testpak", "testcls");
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mTopActivity = cmpt;
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final RunningTaskInfo taskInf = createTaskInfo(1);
+ final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ assertFalse(filter.matches(openTask));
+
+ taskInf.topActivity = cmpt;
+ final TransitionInfo openTaskCmpt = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ assertTrue(filter.matches(openTaskCmpt));
+
+ final TransitionInfo openAct = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, cmpt).build();
+ assertTrue(filter.matches(openAct));
+ }
+
+ @Test
public void testRegisteredRemoteTransition() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index 834385832e4a..b8939e6ff623 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
+import android.content.ComponentName;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -50,20 +51,34 @@ public class TransitionInfoBuilder {
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
- @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
+ @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo,
+ ComponentName activityComponent) {
final TransitionInfo.Change change = new TransitionInfo.Change(
taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */));
change.setMode(mode);
change.setFlags(flags);
change.setTaskInfo(taskInfo);
+ change.setActivityComponent(activityComponent);
return addChange(change);
}
+ /** Add a change to the TransitionInfo */
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
+ return addChange(mode, flags, taskInfo, null /* activityComponent */);
+ }
+
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
ActivityManager.RunningTaskInfo taskInfo) {
return addChange(mode, TransitionInfo.FLAG_NONE, taskInfo);
}
+ /** Add a change to the TransitionInfo */
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ ComponentName activityComponent) {
+ return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent);
+ }
+
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 883c24e78076..9bb5482de715 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -27,11 +27,12 @@ import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
-import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.util.SparseArray
import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
import android.view.InputChannel
import android.view.InputMonitor
import android.view.InsetsSource
@@ -40,8 +41,6 @@ import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.window.TransitionInfo
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -53,8 +52,6 @@ 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.desktopmode.DesktopTasksController
-import com.android.wm.shell.recents.RecentsTransitionHandler
-import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -76,6 +73,8 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import java.util.Optional
import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.spy
/** Tests of [DesktopModeWindowDecorViewModel] */
@@ -100,12 +99,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockShellController: ShellController
@Mock private lateinit var mockShellExecutor: ShellExecutor
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
+ @Mock private lateinit var mockWindowManager: IWindowManager
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
+ private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
@@ -114,12 +114,15 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Before
fun setUp() {
shellInit = ShellInit(mockShellExecutor)
+ windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
mContext,
+ mockShellExecutor,
mockMainHandler,
mockMainChoreographer,
shellInit,
mockShellCommandHandler,
+ mockWindowManager,
mockTaskOrganizer,
mockDisplayController,
mockShellController,
@@ -127,11 +130,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
- mockRecentsTransitionHandler,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ windowDecorByTaskIdSpy
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -275,48 +278,6 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testRelayoutBlockedDuringRecentsTransition() {
- val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
- verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
-
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // Now call back when a Recents transition starts.
- recentsCaptor.firstValue.onTransitionStarted(transition)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
-
- @Test
- fun testRelayoutBlockedDuringKeyguardTransition() {
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
- val transitionInfo = mock(TransitionInfo::class.java)
- val transitionChange = mock(TransitionInfo.Change::class.java)
- val taskInfo = mock(RunningTaskInfo()::class.java)
-
- // Replicate a keyguard going away transition for a task
- whenever(transitionInfo.getFlags())
- .thenReturn(WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
- whenever(transitionChange.getMode()).thenReturn(WindowManager.TRANSIT_TO_FRONT)
- whenever(transitionChange.getTaskInfo()).thenReturn(taskInfo)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // OnTransition ready is called when a keyguard going away transition happens
- desktopModeWindowDecorViewModel
- .onTransitionReady(transition, transitionInfo, transitionChange)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
- @Test
fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
val decoration = setUpMockDecorationForTask(task)
@@ -377,6 +338,19 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(decoration, times(1)).relayout(task)
}
+ @Test
+ fun testDestroyWindowDecoration_closesBeforeCleanup() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy)
+
+ onTaskOpening(task)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+ inOrder.verify(decoration).close()
+ inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 18fcdd00df9d..9e62bd254ac5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -16,16 +16,30 @@
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.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.Handler;
+import android.os.SystemProperties;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
import android.view.Choreographer;
import android.view.Display;
import android.view.SurfaceControl;
@@ -34,14 +48,17 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -57,6 +74,13 @@ import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeWindowDecorationTests extends ShellTestCase {
+ private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_window_shadows";
+ private static final String FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_window_shadows_focused_window";
+ private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_rounded_corners";
+
@Mock
private DisplayController mMockDisplayController;
@Mock
@@ -79,14 +103,29 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+ @Mock
+ private TypedArray mMockRoundedCornersRadiusArray;
private final Configuration mConfiguration = new Configuration();
+ private TestableContext mTestableContext;
+
+ /** Set up run before test class. */
+ @BeforeClass
+ public static void setUpClass() {
+ // Reset the sysprop settings before running the test.
+ SystemProperties.set(USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+ SystemProperties.set(FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+ SystemProperties.set(USE_ROUNDED_CORNERS_SYSPROP_KEY, "");
+ }
+
@Before
public void setUp() {
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
any(), any(), any());
doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
+ mTestableContext = new TestableContext(mContext);
+ mTestableContext.ensureTestableResources();
}
@Test
@@ -105,6 +144,103 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
+ }
+
+ @Test
+ public void updateRelayoutParams_freeformAndTransparent_allowsInputFallthrough() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setStatusBarAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mAllowCaptionInputFallthrough).isTrue();
+ }
+
+ @Test
+ public void updateRelayoutParams_freeformButOpaque_disallowsInputFallthrough() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setStatusBarAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse();
+ }
+
+ @Test
+ public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse();
+ }
+
+ private void fillRoundedCornersResources(int fillValue) {
+ when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
+ .thenReturn(fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius, fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerTopRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius_top, fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerBottomRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius_bottom, fillValue);
+ }
+
+
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
@@ -123,6 +259,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(visible)
.build();
+ taskInfo.topActivityInfo = new ActivityInfo();
+ taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo();
taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04aecf6c..e60be7186b1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@ class DragPositioningCallbackUtilityTest {
}
@Test
+ fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100)
+
+ DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ validDragArea)
+ assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+ assertThat(repositionTaskBounds.right)
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ assertThat(repositionTaskBounds.bottom)
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ }
+
+ @Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2ee8b3..de6903d9a06a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -10,6 +10,7 @@ import android.view.Surface
import android.view.Surface.ROTATION_270
import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
+import android.view.WindowManager
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
@@ -18,13 +19,17 @@ 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.DisplayLayout
+import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
@@ -34,6 +39,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
import java.util.function.Supplier
import org.mockito.Mockito.`when` as whenever
@@ -50,6 +56,8 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
@Mock
private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock
+ private lateinit var mockTransitions: Transitions
+ @Mock
private lateinit var mockWindowDecoration: WindowDecoration<*>
@Mock
private lateinit var mockDragStartListener: DragPositioningCallbackUtility.DragStartListener
@@ -69,6 +77,8 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
@Mock
private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock
+ private lateinit var mockTransitionBinder: IBinder
private lateinit var taskPositioner: FluidResizeTaskPositioner
@@ -103,11 +113,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockTransitions.startTransition(anyInt(), any(), any()))
+ .doReturn(mockTransitionBinder)
taskPositioner = FluidResizeTaskPositioner(
mockShellTaskOrganizer,
+ mockTransitions,
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
@@ -117,7 +131,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
}
@Test
- fun testDragResize_notMove_skipsTransactionOnEnd() {
+ fun testDragResize_notMove_skipsTransitionOnEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -129,16 +143,16 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.top.toFloat() + 10
)
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
+ }}, eq(taskPositioner))
}
@Test
- fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+ fun testDragResize_noEffectiveMove_skipsTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -150,21 +164,28 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.top.toFloat()
)
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+
taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
+ }}, eq(taskPositioner))
}
@Test
- fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+ fun testDragResize_hasEffectiveMove_issuesTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -191,13 +212,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
)
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.top += 10
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- })
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }}, eq(taskPositioner))
}
@Test
@@ -225,6 +246,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
change.dragResizing
}
})
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }}, eq(taskPositioner))
}
@Test
@@ -252,13 +280,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
change.dragResizing
}
})
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
!change.dragResizing
- }
- })
+ }}, eq(taskPositioner))
}
@Test
@@ -269,7 +297,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.top.toFloat()
)
- // Resize to width of 95px and height of 5px with min width of 10px
+ // Resize to width of 95px and height of 5px with min height of 10px
val newX = STARTING_BOUNDS.right.toFloat() - 5
val newY = STARTING_BOUNDS.top.toFloat() + 95
taskPositioner.onDragPositioningMove(
@@ -565,12 +593,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningEnd(newX, newY)
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
+ }}, eq(taskPositioner))
}
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
@@ -649,14 +677,46 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
)
// Verify task's top bound is set to stable bounds top since dragged outside stable bounds
// but not in disallowed end bounds area.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
STABLE_BOUNDS_LANDSCAPE.top
- }
- })
+ }}, eq(taskPositioner))
+ }
+
+ @Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }}, eq(taskPositioner))
}
@Test
@@ -708,6 +768,59 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
}
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() {
+ assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ assertTrue(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterAbortedTransition() {
+ performDrag(STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.onTransitionConsumed(mockTransitionBinder, true /* aborted */,
+ mockTransaction)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterNonAbortedTransition() {
+ performDrag(STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.onTransitionConsumed(mockTransitionBinder, false /* aborted */,
+ mockTransaction)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
private fun performDrag(
startX: Float,
startY: Float,
@@ -761,5 +874,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf14324a..86253f35a51d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -26,6 +26,7 @@ import android.view.Surface.ROTATION_270
import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTaskOrganizer
@@ -33,10 +34,12 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +88,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock
private lateinit var mockTransaction: SurfaceControl.Transaction
@Mock
+ private lateinit var mockTransitionBinder: IBinder
+ @Mock
+ private lateinit var mockTransitionInfo: TransitionInfo
+ @Mock
+ private lateinit var mockFinishCallback: TransitionFinishCallback
+ @Mock
private lateinit var mockTransitions: Transitions
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -118,6 +127,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -134,13 +144,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
- fun testDragResize_noMove_showsResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat(),
@@ -152,7 +162,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
eq(taskPositioner))
- verify(mockDesktopWindowDecoration).hideResizeVeil()
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
}
@Test
@@ -187,13 +197,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
verify(mockDesktopWindowDecoration, never()).createResizeVeil()
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- })
+ change.configuration.windowConfiguration.bounds == rectAfterEnd }},
+ eq(taskPositioner))
}
@Test
@@ -203,7 +212,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.right.toFloat() + 10,
@@ -213,6 +221,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
val rectAfterMove = Rect(STARTING_BOUNDS)
rectAfterMove.right += 10
rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -228,7 +237,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.right += 10
rectAfterEnd.top += 10
- verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -244,7 +253,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat(),
@@ -368,14 +376,44 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
)
// Verify task's top bound is set to stable bounds top since dragged outside stable bounds
// but not in disallowed end bounds area.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }
- })
+ STABLE_BOUNDS_LANDSCAPE.top }},
+ eq(taskPositioner))
+ }
+
+ @Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left }},
+ eq(taskPositioner))
}
@Test
@@ -423,6 +461,47 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
verify(mockDisplayLayout, times(2)).getStableBounds(any())
}
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() {
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() {
+ performDrag(
+ STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(),
+ STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.startAnimation(mockTransitionBinder, mockTransitionInfo, mockTransaction,
+ mockTransaction, mockFinishCallback)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
private fun performDrag(
startX: Float,
startY: Float,
@@ -470,5 +549,11 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74b8d17..228b25ccb1ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -32,6 +32,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
@@ -258,14 +259,10 @@ public class WindowDecorationTests extends ShellTestCase {
any(),
eq(0 /* index */),
eq(WindowInsets.Type.captionBar()),
- eq(new Rect(100, 300, 400, 364)));
+ eq(new Rect(100, 300, 400, 364)),
+ any());
}
- verify(mMockSurfaceControlFinishT)
- .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
- TASK_POSITION_IN_PARENT.y);
- verify(mMockSurfaceControlFinishT)
- .setWindowCrop(mMockTaskSurface, 300, 100);
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
@@ -573,9 +570,9 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any());
+ eq(0) /* index */, eq(captionBar()), any(), any());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
}
@Test
@@ -642,6 +639,66 @@ public class WindowDecorationTests extends ShellTestCase {
eq(0) /* index */, eq(mandatorySystemGestures()));
}
+ @Test
+ public void testTaskPositionAndCropNotSetWhenFalse() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+
+ mRelayoutParams.mSetTaskPositionAndCrop = false;
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT, never()).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ verify(mMockSurfaceControlFinishT, never()).setPosition(
+ eq(mMockTaskSurface), anyFloat(), anyFloat());
+ verify(mMockSurfaceControlFinishT, never()).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testTaskPositionAndCropSetWhenSetTrue() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mSetTaskPositionAndCrop = true;
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ verify(mMockSurfaceControlFinishT).setPosition(
+ eq(mMockTaskSurface), anyFloat(), anyFloat());
+ verify(mMockSurfaceControlFinishT).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ }
+
+
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, mMockTaskSurface, mWindowConfiguration,
@@ -702,6 +759,11 @@ public class WindowDecorationTests extends ShellTestCase {
relayout(taskInfo, false /* applyStartTransactionOnDraw */);
}
+ @Override
+ Rect calculateValidDragArea() {
+ return null;
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
@@ -711,15 +773,13 @@ public class WindowDecorationTests extends ShellTestCase {
private WindowDecoration.AdditionalWindow addTestWindow() {
final Resources resources = mDecorWindowContext.getResources();
- int x = mRelayoutParams.mCaptionX;
- int y = mRelayoutParams.mCaptionY;
int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.desktop_mode_window_decor_handle_menu, name,
- mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
- width, height);
+ mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, 0 /* x */,
+ 0 /* y */, width, height);
return additionalWindow;
}
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2f28363aedc7..77800a305f02 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -31,6 +31,12 @@ license {
],
}
+cc_aconfig_library {
+ name: "backup_flags_cc_lib",
+ host_supported: true,
+ aconfig_declarations: "backup_flags",
+}
+
cc_defaults {
name: "libandroidfw_defaults",
cpp_std: "gnu++2b",
@@ -115,7 +121,10 @@ cc_library {
"libutils",
"libz",
],
- static_libs: ["libziparchive_for_incfs"],
+ static_libs: [
+ "libziparchive_for_incfs",
+ "backup_flags_cc_lib",
+ ],
static: {
enabled: false,
},
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index f0c639574a9f..49254d1c6f6e 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -81,7 +81,7 @@ ApkAssetsPtr ApkAssets::LoadOverlay(const std::string& idmap_path, package_prope
std::string overlay_path(loaded_idmap->OverlayApkPath());
auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
std::unique_ptr<AssetsProvider> overlay_assets;
- if (IsFabricatedOverlay(fd)) {
+ if (IsFabricatedOverlayName(overlay_path) && IsFabricatedOverlay(fd)) {
// Fabricated overlays do not contain resource definitions. All of the overlay resource values
// are defined inline in the idmap.
overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
@@ -137,8 +137,7 @@ ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
return {};
}
loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
- } else if (loaded_idmap != nullptr &&
- IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+ } else if (loaded_idmap != nullptr && IsFabricatedOverlay(loaded_idmap->OverlayApkPath())) {
loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
} else {
loaded_arsc = LoadedArsc::CreateEmpty();
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 8748dab581bb..46f636e2ae7f 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -117,6 +117,10 @@ bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_cache
return true;
}
+void AssetManager2::PresetApkAssets(ApkAssetsList apk_assets) {
+ BuildDynamicRefTable(apk_assets);
+}
+
bool AssetManager2::SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets,
bool invalidate_caches) {
return SetApkAssets(ApkAssetsList(apk_assets.begin(), apk_assets.size()), invalidate_caches);
@@ -432,13 +436,18 @@ bool AssetManager2::ContainsAllocatedTable() const {
return false;
}
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations,
+ bool force_refresh) {
int diff = 0;
- if (configurations_.size() != configurations.size()) {
+ if (force_refresh) {
diff = -1;
} else {
- for (int i = 0; i < configurations_.size(); i++) {
- diff |= configurations_[i].diff(configurations[i]);
+ if (configurations_.size() != configurations.size()) {
+ diff = -1;
+ } else {
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
+ }
}
}
configurations_ = std::move(configurations);
@@ -775,8 +784,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
bool has_locale = false;
if (result->config.locale == 0) {
if (default_locale_ != 0) {
- ResTable_config conf;
- conf.locale = default_locale_;
+ ResTable_config conf = {.locale = default_locale_};
// Since we know conf has a locale and only a locale, match will tell us if that locale
// matches
has_locale = conf.match(config);
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index 1a6a952492f6..a1e7c2ffc1b1 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -36,6 +36,9 @@
#include <utils/KeyedVector.h>
#include <utils/String8.h>
+#include <com_android_server_backup.h>
+namespace backup_flags = com::android::server::backup;
+
namespace android {
#define MAGIC0 0x70616e53 // Snap
@@ -214,7 +217,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8&
{
LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.c_str(), mode);
- const int bufsize = 4*1024;
+ const int bufsize = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (4*1024);
int err;
int amt;
int fileSize;
@@ -550,7 +553,8 @@ int write_tarfile(const String8& packageName, const String8& domain,
}
// read/write up to this much at a time.
- const size_t BUFSIZE = 32 * 1024;
+ const size_t BUFSIZE = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (32*1024);
+
char* buf = (char *)calloc(1,BUFSIZE);
const size_t PAXHEADER_OFFSET = 512;
const size_t PAXHEADER_SIZE = 512;
@@ -726,7 +730,7 @@ done:
-#define RESTORE_BUF_SIZE (8*1024)
+const size_t RESTORE_BUF_SIZE = backup_flags::enable_max_size_writes_to_pipes() ? 64*1024 : 8*1024;
RestoreHelperBase::RestoreHelperBase()
{
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 5f98b8f8db43..982419059ead 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -18,8 +18,10 @@
#include "androidfw/Idmap.h"
+#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "android-base/utf8.h"
#include "androidfw/misc.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -250,7 +252,12 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size
}
} // namespace
-LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header,
+// O_PATH is a lightweight way of creating an FD, only exists on Linux
+#ifndef O_PATH
+#define O_PATH (0)
+#endif
+
+LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
const Idmap_target_entry_inline* target_inline_entries,
@@ -267,10 +274,10 @@ LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header,
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
- idmap_path_(std::move(idmap_path)),
+ idmap_fd_(android::base::utf8::open(idmap_path.c_str(), O_RDONLY|O_CLOEXEC|O_BINARY|O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
+ idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
ATRACE_CALL();
@@ -368,7 +375,7 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
}
bool LoadedIdmap::IsUpToDate() const {
- return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str());
+ return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
}
} // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e074271b..d9166a16cdea 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstddef>
#include <limits>
+#include <optional>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@ namespace {
// contiguous block of memory to store both the TypeSpec struct and
// the Type structs.
struct TypeSpecBuilder {
- explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+ explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+ type_entries.reserve(dtohs(header_->typesCount));
+ }
void AddType(incfs::verified_map_ptr<ResTable_type> type) {
TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@ struct TypeSpecBuilder {
}
TypeSpec Build() {
+ type_entries.shrink_to_fit();
return {header_, std::move(type_entries)};
}
@@ -450,7 +454,8 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
package_property_t property_flags) {
ATRACE_NAME("LoadedPackage::Load");
- std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+ const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+ std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
@@ -499,7 +504,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
- std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+ std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
@@ -567,14 +572,14 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
return {};
}
- if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+ if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
return {};
}
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
- if (builder_ptr == nullptr) {
- builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+ auto& maybe_type_builder = type_builder_map[type_spec->id];
+ if (!maybe_type_builder) {
+ maybe_type_builder.emplace(type_spec.verified());
loaded_package->resource_ids_.set(type_spec->id, entry_count);
} else {
LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
// Type chunks must be preceded by their TypeSpec chunks.
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
- if (builder_ptr != nullptr) {
- builder_ptr->AddType(type.verified());
+ auto& maybe_type_builder = type_builder_map[type->id];
+ if (maybe_type_builder) {
+ maybe_type_builder->AddType(type.verified());
} else {
LOG(ERROR) << StringPrintf(
"RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992becda7c..a3dd9833219e 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -54,6 +54,8 @@
#define INT32_MAX ((int32_t)(2147483647))
#endif
+using namespace std::literals;
+
namespace android {
#if defined(_WIN32)
@@ -237,12 +239,24 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs
fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
}
-bool IsFabricatedOverlay(const std::string& path) {
- return IsFabricatedOverlay(path.c_str());
+bool IsFabricatedOverlayName(std::string_view path) {
+ static constexpr auto suffixFrro = ".frro"sv;
+ static constexpr auto suffixIdmap = ".frro@idmap"sv;
+
+ return (path.size() > suffixFrro.size() && path.ends_with(suffixFrro))
+ || (path.size() > suffixIdmap.size() && path.ends_with(suffixIdmap));
}
-bool IsFabricatedOverlay(const char* path) {
- auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+bool IsFabricatedOverlay(std::string_view path) {
+ if (!IsFabricatedOverlayName(path)) {
+ return false;
+ }
+ std::string path_copy;
+ if (path[path.size()] != '\0') {
+ path_copy.assign(path);
+ path = path_copy;
+ }
+ auto fd = base::unique_fd(base::utf8::open(path.data(), O_RDONLY|O_CLOEXEC|O_BINARY));
if (fd < 0) {
return false;
}
@@ -447,15 +461,19 @@ Res_png_9patch* Res_png_9patch::deserialize(void* inData)
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-ResStringPool::ResStringPool()
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
}
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
- setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+ if (optimize_name_lookups) {
+ mIndexLookupCache.emplace();
+ }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+ bool optimize_name_lookups)
+ : ResStringPool(optimize_name_lookups) {
+ setTo(data, size, copyData);
}
ResStringPool::~ResStringPool()
@@ -683,6 +701,14 @@ status_t ResStringPool::setTo(incfs::map_ptr<void> data, size_t size, bool copyD
mStylePoolSize = 0;
}
+ if (mIndexLookupCache) {
+ if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+ mIndexLookupCache->first.reserve(mHeader->stringCount);
+ } else {
+ mIndexLookupCache->second.reserve(mHeader->stringCount);
+ }
+ }
+
return (mError=NO_ERROR);
}
@@ -708,6 +734,10 @@ void ResStringPool::uninit()
free(mOwnedData);
mOwnedData = NULL;
}
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.clear();
+ mIndexLookupCache->second.clear();
+ }
}
/**
@@ -824,11 +854,11 @@ base::expected<StringPiece16, NullOrIOError> ResStringPool::stringAt(size_t idx)
// encLen must be less than 0x7FFF due to encoding.
if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
- AutoMutex lock(mDecodeLock);
+ AutoMutex lock(mCachesLock);
- if (mCache != NULL && mCache[idx] != NULL) {
- return StringPiece16(mCache[idx], *u16len);
- }
+ if (mCache != NULL && mCache[idx] != NULL) {
+ return StringPiece16(mCache[idx], *u16len);
+ }
// Retrieve the actual length of the utf8 string if the
// encoded length was truncated
@@ -1093,12 +1123,24 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_
// block, start searching at the back.
String8 str8(str, strLen);
const size_t str8Len = str8.size();
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+ it != mIndexLookupCache->first.end()) {
+ return it->second;
+ }
+ }
+
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece, NullOrIOError> s = string8At(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.insert({*s, i});
+ }
if (kDebugStringPoolNoisy) {
ALOGI("Looking at %s, i=%d\n", s->data(), i);
}
@@ -1151,20 +1193,32 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_
// most often this happens because we want to get IDs for style
// span tags; since those always appear at the end of the string
// block, start searching at the back.
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->second.find({str, strLen});
+ it != mIndexLookupCache->second.end()) {
+ return it->second;
+ }
+ }
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+ ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
}
- if (s.has_value() && strLen == s->size() &&
- strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+ if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->second.insert({*s, i});
+ }
+ if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
+ ALOGI("MATCH16!");
}
return i;
+ }
}
}
}
@@ -7279,9 +7333,6 @@ class IdmapTypeMapping {
public:
void add(uint32_t targetResId, uint32_t overlayResId) {
uint8_t targetTypeId = Res_GETTYPE(targetResId);
- if (mData.find(targetTypeId) == mData.end()) {
- mData.emplace(targetTypeId, std::set<std::pair<uint32_t, uint32_t>>());
- }
auto& entries = mData[targetTypeId];
entries.insert(std::make_pair(targetResId, overlayResId));
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index d9ff35b49e0a..17a8ba6c03bd 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -124,6 +124,9 @@ class AssetManager2 {
// new resource IDs.
bool SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches = true);
bool SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches = true);
+ // This one is an optimization - it skips all calculations for applying the currently set
+ // configuration, expecting a configuration update later with a forced refresh.
+ void PresetApkAssets(ApkAssetsList apk_assets);
const ApkAssetsPtr& GetApkAssets(ApkAssetsCookie cookie) const;
int GetApkAssetsCount() const {
@@ -156,7 +159,7 @@ class AssetManager2 {
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfigurations(std::vector<ResTable_config> configurations);
+ void SetConfigurations(std::vector<ResTable_config> configurations, bool force_refresh = false);
inline const std::vector<ResTable_config>& GetConfigurations() const {
return configurations_;
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 60689128dffb..c32a38ee9ec2 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -23,6 +23,7 @@
#include <variant>
#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
#include "androidfw/ResourceTypes.h"
@@ -71,7 +72,7 @@ class OverlayDynamicRefTable : public DynamicRefTable {
// Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
// resource.
- virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+ status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
const Idmap_data_header* data_header_;
const Idmap_overlay_entry* entries_;
@@ -159,11 +160,6 @@ class LoadedIdmap {
// Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
- // Returns the path to the IDMAP.
- std::string_view IdmapPath() const {
- return idmap_path_;
- }
-
// Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
std::string_view OverlayApkPath() const {
return overlay_apk_path_;
@@ -203,7 +199,7 @@ class LoadedIdmap {
const Idmap_overlay_entry* overlay_entries_;
const std::unique_ptr<ResStringPool> string_pool_;
- std::string idmap_path_;
+ android::base::unique_fd idmap_fd_;
std::string_view overlay_apk_path_;
std::string_view target_apk_path_;
time_t idmap_last_mod_time_;
@@ -211,7 +207,7 @@ class LoadedIdmap {
private:
DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
- explicit LoadedIdmap(std::string&& idmap_path,
+ explicit LoadedIdmap(const std::string& idmap_path,
const Idmap_header* header,
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a7287187781..413b27829474 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@ enum : package_property_t {
// The apk assets only contain the overlayable declarations information.
PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+ // Optimize the resource lookups by name via an in-memory lookup table.
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
};
struct OverlayableInfo {
@@ -285,7 +288,9 @@ class LoadedPackage {
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- LoadedPackage() = default;
+ explicit LoadedPackage(bool optimize_name_lookups = false)
+ : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+ }
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fdff469..c2648909386c 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
#include <android-base/expected.h>
#include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
#include <utils/threads.h>
#include <stdint.h>
#include <sys/types.h>
-#include <android/configuration.h>
-
#include <array>
#include <map>
#include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
namespace android {
@@ -58,8 +59,8 @@ constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endi
constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
// Returns whether or not the path represents a fabricated overlay.
-bool IsFabricatedOverlay(const std::string& path);
-bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlayName(std::string_view path);
+bool IsFabricatedOverlay(std::string_view path);
bool IsFabricatedOverlay(android::base::borrowed_fd fd);
/**
@@ -512,23 +513,24 @@ struct ResStringPool_span
class ResStringPool
{
public:
- ResStringPool();
- ResStringPool(const void* data, size_t size, bool copyData=false);
- virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+ bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
- void setToEmpty();
- status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
- status_t getError() const;
+ status_t getError() const;
- void uninit();
+ void uninit();
- // Return string entry as UTF16; if the pool is UTF8, the string will
- // be converted before returning.
- inline base::expected<StringPiece16, NullOrIOError> stringAt(
- const ResStringPool_ref& ref) const {
- return stringAt(ref.index);
- }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+ return stringAt(ref.index);
+ }
virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
// Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@ private:
void* mOwnedData;
incfs::verified_map_ptr<ResStringPool_header> mHeader;
size_t mSize;
- mutable Mutex mDecodeLock;
+ mutable Mutex mCachesLock;
incfs::map_ptr<uint32_t> mEntries;
incfs::map_ptr<uint32_t> mEntryStyles;
incfs::map_ptr<void> mStrings;
@@ -566,6 +568,10 @@ private:
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
+ mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+ std::unordered_map<std::u16string_view, int>>>
+ mIndexLookupCache;
+
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
};
@@ -1401,8 +1407,8 @@ struct ResTable_typeSpec
// Must be 0.
uint8_t res0;
- // Must be 0.
- uint16_t res1;
+ // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+ uint16_t typesCount;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index d40d24ede769..077609d20d55 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -43,6 +43,8 @@ typedef enum FileType {
FileType getFileType(const char* fileName);
/* get the file's modification date; returns -1 w/errno set on failure */
time_t getFileModDate(const char* fileName);
+/* same, but also returns -1 if the file has already been deleted */
+time_t getFileModDate(int fd);
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index d3949e9cf69f..93dcaf549a90 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -76,13 +76,23 @@ FileType getFileType(const char* fileName)
/*
* Get a file's modification date.
*/
-time_t getFileModDate(const char* fileName)
-{
+time_t getFileModDate(const char* fileName) {
struct stat sb;
+ if (stat(fileName, &sb) < 0) {
+ return (time_t)-1;
+ }
+ return sb.st_mtime;
+}
- if (stat(fileName, &sb) < 0)
- return (time_t) -1;
-
+time_t getFileModDate(int fd) {
+ struct stat sb;
+ if (fstat(fd, &sb) < 0) {
+ return (time_t)-1;
+ }
+ if (sb.st_nlink <= 0) {
+ errno = ENOENT;
+ return (time_t)-1;
+ }
return sb.st_mtime;
}
diff --git a/libs/hostgraphics/ADisplay.cpp b/libs/hostgraphics/ADisplay.cpp
new file mode 100644
index 000000000000..9cc1f40184e3
--- /dev/null
+++ b/libs/hostgraphics/ADisplay.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apex/display.h>
+#include <utils/Errors.h>
+
+namespace android::display::impl {
+
+/**
+ * Implementation of ADisplayConfig
+ */
+struct DisplayConfigImpl {
+ /**
+ * The width in pixels of the display configuration.
+ */
+ int32_t width{1080};
+
+ /**
+ * The height in pixels of the display configuration.
+ */
+
+ int32_t height{1920};
+
+ /**
+ * The refresh rate of the display configuration, in frames per second.
+ */
+ float fps{60.0};
+
+ /**
+ * The vsync offset at which surfaceflinger runs, in nanoseconds.
+ */
+ int64_t sfOffset{0};
+
+ /**
+ * The vsync offset at which applications run, in nanoseconds.
+ */
+ int64_t appOffset{0};
+};
+
+// DisplayConfigImpl allocation is not managed through C++ memory apis, so
+// preventing calling the destructor here.
+static_assert(std::is_trivially_destructible<DisplayConfigImpl>::value);
+
+/**
+ * Implementation of ADisplay
+ */
+struct DisplayImpl {
+ /**
+ * The type of the display, i.e. whether it is an internal or external
+ * display.
+ */
+ ADisplayType type;
+
+ /**
+ * The preferred WCG dataspace
+ */
+ ADataSpace wcgDataspace;
+
+ /**
+ * The preferred WCG pixel format
+ */
+ AHardwareBuffer_Format wcgPixelFormat;
+
+ /**
+ * The config for this display.
+ */
+ DisplayConfigImpl config;
+};
+
+// DisplayImpl allocation is not managed through C++ memory apis, so
+// preventing calling the destructor here.
+static_assert(std::is_trivially_destructible<DisplayImpl>::value);
+
+} // namespace android::display::impl
+
+using namespace android;
+using namespace android::display::impl;
+
+namespace android {
+
+int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) {
+ // This is running on host, so there are no physical displays available.
+ // Create 1 fake display instead.
+ DisplayImpl** const impls = reinterpret_cast<DisplayImpl**>(
+ malloc(sizeof(DisplayImpl*) + sizeof(DisplayImpl)));
+ DisplayImpl* const displayData = reinterpret_cast<DisplayImpl*>(impls + 1);
+
+ displayData[0] = DisplayImpl{ADisplayType::DISPLAY_TYPE_INTERNAL,
+ ADataSpace::ADATASPACE_UNKNOWN,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ DisplayConfigImpl()};
+ impls[0] = displayData;
+ *outDisplays = reinterpret_cast<ADisplay**>(impls);
+ return 1;
+}
+
+void ADisplay_release(ADisplay** displays) {
+ if (displays == nullptr) {
+ return;
+ }
+ free(displays);
+}
+
+float ADisplay_getMaxSupportedFps(ADisplay* display) {
+ DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+ return impl->config.fps;
+}
+
+ADisplayType ADisplay_getDisplayType(ADisplay* display) {
+ return reinterpret_cast<DisplayImpl*>(display)->type;
+}
+
+void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outDataspace,
+ AHardwareBuffer_Format* outPixelFormat) {
+ DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+ *outDataspace = impl->wcgDataspace;
+ *outPixelFormat = impl->wcgPixelFormat;
+}
+
+int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) {
+ DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+ *outConfig = reinterpret_cast<ADisplayConfig*>(&impl->config);
+ return OK;
+}
+
+int32_t ADisplayConfig_getWidth(ADisplayConfig* config) {
+ return reinterpret_cast<DisplayConfigImpl*>(config)->width;
+}
+
+int32_t ADisplayConfig_getHeight(ADisplayConfig* config) {
+ return reinterpret_cast<DisplayConfigImpl*>(config)->height;
+}
+
+float ADisplayConfig_getFps(ADisplayConfig* config) {
+ return reinterpret_cast<DisplayConfigImpl*>(config)->fps;
+}
+
+int64_t ADisplayConfig_getCompositorOffsetNanos(ADisplayConfig* config) {
+ return reinterpret_cast<DisplayConfigImpl*>(config)->sfOffset;
+}
+
+int64_t ADisplayConfig_getAppVsyncOffsetNanos(ADisplayConfig* config) {
+ return reinterpret_cast<DisplayConfigImpl*>(config)->appOffset;
+}
+
+} // namespace android
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index f166fdeafa41..4407af68de99 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -22,6 +22,7 @@ cc_library_host_static {
srcs: [
":libui_host_common",
+ "ADisplay.cpp",
"Fence.cpp",
"HostBufferQueue.cpp",
"PublicFormat.cpp",
@@ -32,16 +33,21 @@ cc_library_host_static {
// When frameworks/native/include will be removed from the list of automatic includes.
// We will have to copy necessary headers with a pre-build step (generated headers).
".",
- "frameworks/native/libs/nativebase/include",
- "frameworks/native/libs/nativewindow/include",
"frameworks/native/libs/arect/include",
"frameworks/native/libs/ui/include_private",
],
+
+ header_libs: [
+ "libnativebase_headers",
+ "libnativedisplay_headers",
+ "libnativewindow_headers",
+ ],
+
export_include_dirs: ["."],
target: {
windows: {
enabled: true,
- }
+ },
},
}
diff --git a/libs/hostgraphics/OWNERS b/libs/hostgraphics/OWNERS
new file mode 100644
index 000000000000..41977f693a04
--- /dev/null
+++ b/libs/hostgraphics/OWNERS
@@ -0,0 +1,4 @@
+include /libs/hwui/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index de1ba00211d3..2573931c8543 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -50,6 +50,8 @@ public:
virtual int unlockAndPost() { return 0; }
virtual int query(int what, int* value) const { return 0; }
+ virtual void destroy() {}
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 14e865366371..54f94f5c4b14 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -38,6 +38,7 @@ aconfig_declarations {
cc_aconfig_library {
name: "hwui_flags_cc_lib",
+ host_supported: true,
aconfig_declarations: "hwui_flags",
}
@@ -109,12 +110,15 @@ cc_defaults {
"libbase",
"libharfbuzz_ng",
"libminikin",
+ "server_configurable_flags",
],
static_libs: [
"libui-types",
],
+ whole_static_libs: ["hwui_flags_cc_lib"],
+
target: {
android: {
shared_libs: [
@@ -146,12 +150,12 @@ cc_defaults {
"libstatspull_lazy",
"libstatssocket_lazy",
"libtonemap",
- "hwui_flags_cc_lib",
],
},
host: {
static_libs: [
"libandroidfw",
+ "libhostgraphics",
"libutils",
],
},
@@ -342,6 +346,7 @@ cc_defaults {
"jni/android_util_PathParser.cpp",
"jni/Bitmap.cpp",
+ "jni/BitmapRegionDecoder.cpp",
"jni/BufferUtils.cpp",
"jni/HardwareBufferHelpers.cpp",
"jni/BitmapFactory.cpp",
@@ -417,7 +422,6 @@ cc_defaults {
"jni/android_graphics_TextureLayer.cpp",
"jni/android_graphics_HardwareRenderer.cpp",
"jni/android_graphics_HardwareBufferRenderer.cpp",
- "jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
@@ -499,6 +503,17 @@ cc_library_headers {
],
header_libs: ["android_graphics_jni_headers"],
export_header_lib_headers: ["android_graphics_jni_headers"],
+ target: {
+ android: {
+ export_include_dirs: ["platform/android"],
+ },
+ host: {
+ export_include_dirs: ["platform/host"],
+ },
+ windows: {
+ enabled: true,
+ },
+ },
}
cc_defaults {
@@ -536,6 +551,7 @@ cc_defaults {
"utils/Blur.cpp",
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
+ "utils/StringUtils.cpp",
"utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
@@ -543,13 +559,19 @@ cc_defaults {
"AnimatorManager.cpp",
"CanvasTransform.cpp",
"DamageAccumulator.cpp",
+ "DeviceInfo.cpp",
+ "FrameInfo.cpp",
+ "FrameInfoVisualizer.cpp",
+ "FrameMetricsReporter.cpp",
"Gainmap.cpp",
"Interpolator.cpp",
+ "JankTracker.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"Mesh.cpp",
"MemoryPolicy.cpp",
"PathParser.cpp",
+ "ProfileData.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",
"PropertyValuesHolder.cpp",
@@ -567,12 +589,13 @@ cc_defaults {
export_proto_headers: true,
},
+ header_libs: ["libandroid_headers_private"],
+
target: {
android: {
- header_libs: [
- "libandroid_headers_private",
- "libtonemap_headers",
- ],
+ header_libs: ["libtonemap_headers"],
+
+ local_include_dirs: ["platform/android"],
srcs: [
"hwui/AnimatedImageThread.cpp",
@@ -603,19 +626,12 @@ cc_defaults {
"thread/CommonPool.cpp",
"utils/GLUtils.cpp",
"utils/NdkUtils.cpp",
- "utils/StringUtils.cpp",
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
- "DeviceInfo.cpp",
- "FrameInfo.cpp",
- "FrameInfoVisualizer.cpp",
"HardwareBitmapUploader.cpp",
"HWUIProperties.sysprop",
- "JankTracker.cpp",
- "FrameMetricsReporter.cpp",
"Layer.cpp",
"LayerUpdateQueue.cpp",
- "ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
@@ -627,11 +643,25 @@ cc_defaults {
cflags: ["-Wno-implicit-fallthrough"],
},
host: {
+ header_libs: [
+ "libnativebase_headers",
+ "libnativedisplay_headers",
+ ],
+
+ local_include_dirs: ["platform/host"],
+
srcs: [
- "utils/HostColorSpace.cpp",
+ "platform/host/renderthread/CacheManager.cpp",
+ "platform/host/renderthread/RenderThread.cpp",
+ "platform/host/ProfileDataContainer.cpp",
+ "platform/host/Readback.cpp",
+ "platform/host/WebViewFunctorManager.cpp",
],
- export_static_lib_headers: [
- "libarect",
+
+ cflags: [
+ "-DHWUI_NULL_GPU",
+ "-DNULL_GPU_MAX_TEXTURE_SIZE=4096",
+ "-Wno-unused-private-field",
],
},
},
@@ -669,6 +699,7 @@ cc_defaults {
header_libs: ["libandroid_headers_private"],
target: {
android: {
+ local_include_dirs: ["platform/android"],
shared_libs: [
"libgui",
"libui",
@@ -704,7 +735,6 @@ cc_test {
],
shared_libs: [
"libmemunreachable",
- "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 4d020c567972..5f5ffe97e953 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -21,6 +21,7 @@
#include <include/gpu/GrDirectContext.h>
#include <include/gpu/GrBackendSurface.h>
#include <include/gpu/MutableTextureState.h>
+#include <include/gpu/vk/VulkanMutableTextureState.h>
#include "renderthread/RenderThread.h"
#include "utils/Color.h"
#include "utils/PaintUtils.h"
@@ -142,8 +143,9 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context)
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
- skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
- VK_QUEUE_FAMILY_FOREIGN_EXT);
+ skgpu::MutableTextureState newState = skgpu::MutableTextureStates::MakeVulkan(
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_QUEUE_FAMILY_FOREIGN_EXT);
// The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
// releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index b667daf9c850..30e7a628f1f6 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -137,9 +137,10 @@ static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette)
return palette;
}
- SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;
- color = paint->getColorFilter()->filterColor(color);
- return paletteForColorHSV(color);
+ SkColor4f color = palette == BitmapPalette::Light ? SkColors::kWhite : SkColors::kBlack;
+ sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
+ color = paint->getColorFilter()->filterColor4f(color, srgb.get(), srgb.get());
+ return paletteForColorHSV(color.toSkColor());
}
bool transformPaint(ColorTransform transform, SkPaint* paint) {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d4af0872e31e..a5a841e07d7a 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -23,6 +23,7 @@
#include <mutex>
+#include "Properties.h"
#include "utils/Macros.h"
namespace android {
@@ -60,7 +61,13 @@ public:
static void setWideColorDataspace(ADataSpace dataspace);
static void setSupportFp16ForHdr(bool supportFp16ForHdr);
- static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+ static bool isSupportFp16ForHdr() {
+ if (!Properties::hdr10bitPlus) {
+ return false;
+ }
+
+ return get()->mSupportFp16ForHdr;
+ };
static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index d21f07efe36a..b12486e32bd4 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -26,6 +26,7 @@ X(ClipPath)
X(ClipRect)
X(ClipRRect)
X(ClipRegion)
+X(ClipShader)
X(ResetClip)
X(DrawPaint)
X(DrawBehind)
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049cde925..ac75c077b58f 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@ inline bool deprecate_ui_fonts() {
#endif // __ANDROID__
}
+inline bool letter_spacing_justification() {
+#ifdef __ANDROID__
+ return com_android_text_flags_letter_spacing_justification();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 59f21694fb77..1e53fc2164c7 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -249,6 +249,7 @@ bool FrameInfoVisualizer::consumeProperties() {
}
void FrameInfoVisualizer::dumpData(int fd) {
+#ifdef __ANDROID__
RETURN_IF_PROFILING_DISABLED();
// This method logs the last N frames (where N is <= mDataSize) since the
@@ -268,6 +269,7 @@ void FrameInfoVisualizer::dumpData(int fd) {
durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers),
durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted));
}
+#endif
}
} /* namespace uirenderer */
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 16de21def0e3..27ea15075682 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -378,10 +378,17 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
break;
case kAlpha_8_SkColorType:
formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
- formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
- formatInfo.format = GL_R8;
- formatInfo.type = GL_UNSIGNED_BYTE;
- formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
+ if (formatInfo.isSupported) {
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
+ formatInfo.format = GL_RED;
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
+ } else {
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+ formatInfo.format = GL_RGBA;
+ }
break;
default:
ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 4b0ddd2fa2ef..638a060bdb1c 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -17,10 +17,10 @@
#include "JankTracker.h"
#include <cutils/ashmem.h>
+#include <cutils/trace.h>
#include <errno.h>
#include <inttypes.h>
#include <log/log.h>
-#include <sys/mman.h>
#include <algorithm>
#include <cmath>
@@ -278,7 +278,7 @@ void JankTracker::recomputeThresholds(int64_t frameBudget) REQUIRES(mDataMutex)
void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
const ProfileData* data) {
-
+#ifdef __ANDROID__
if (description) {
switch (description->type) {
case JankTrackerType::Generic:
@@ -296,9 +296,11 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
}
data->dump(fd);
dprintf(fd, "\n");
+#endif
}
void JankTracker::dumpFrames(int fd) {
+#ifdef __ANDROID__
dprintf(fd, "\n\n---PROFILEDATA---\n");
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
dprintf(fd, "%s", FrameInfoNames[i]);
@@ -315,6 +317,7 @@ void JankTracker::dumpFrames(int fd) {
}
}
dprintf(fd, "\n---PROFILEDATA---\n\n");
+#endif
}
void JankTracker::reset() REQUIRES(mDataMutex) {
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 3d0ca0a10851..7be9541f6b99 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -110,6 +110,7 @@ void ProfileData::mergeWith(const ProfileData& other) {
}
void ProfileData::dump(int fd) const {
+#ifdef __ANDROID__
dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
@@ -138,6 +139,7 @@ void ProfileData::dump(int fd) const {
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
});
dprintf(fd, "\n");
+#endif
}
uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6c3172a26751..755332ff66fd 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -38,6 +38,9 @@ namespace hwui_flags {
constexpr bool clip_surfaceviews() {
return false;
}
+constexpr bool hdr_10bit_plus() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -56,6 +59,7 @@ std::optional<std::int32_t> render_ahead() {
bool Properties::debugLayersUpdates = false;
bool Properties::debugOverdraw = false;
+bool Properties::debugTraceGpuResourceCategories = false;
bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
@@ -104,6 +108,7 @@ bool Properties::isSystemOrPersistent = false;
float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
bool Properties::clipSurfaceViews = false;
+bool Properties::hdr10bitPlus = false;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
@@ -151,10 +156,12 @@ bool Properties::load() {
skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
- SkAndroidFrameworkTraceUtil::setEnableTracing(
- base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+ bool skiaBroadTracing = base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false);
+ SkAndroidFrameworkTraceUtil::setEnableTracing(skiaBroadTracing);
SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
+ debugTraceGpuResourceCategories =
+ base::GetBoolProperty(PROPERTY_TRACE_GPU_RESOURCES, skiaBroadTracing);
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
@@ -174,6 +181,7 @@ bool Properties::load() {
clipSurfaceViews =
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+ hdr10bitPlus = hwui_flags::hdr_10bit_plus();
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bca57e9e4678..ec53070f6cb8 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,6 +143,15 @@ enum DebugLevel {
#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
/**
+ * Might split Skia's GPU resource utilization into separate tracing tracks (slow).
+ *
+ * Aggregate total and purgeable numbers will still be reported under a "misc" track when this is
+ * disabled, they just won't be split into distinct categories. Results may vary depending on GPU
+ * backend/API, and the category mappings defined in ATraceMemoryDump's hardcoded sResourceMap.
+ */
+#define PROPERTY_TRACE_GPU_RESOURCES "debug.hwui.trace_gpu_resources"
+
+/**
* Allows broad recording of Skia drawing commands.
*
* If disabled, a very minimal set of trace events *may* be recorded.
@@ -254,6 +263,7 @@ public:
static bool debugLayersUpdates;
static bool debugOverdraw;
+ static bool debugTraceGpuResourceCategories;
static bool showDirtyRegions;
// TODO: Remove after stabilization period
static bool skipEmptyFrames;
@@ -326,6 +336,7 @@ public:
static float maxHdrHeadroomOn8bit;
static bool clipSurfaceViews;
+ static bool hdr10bitPlus;
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3b694c5d399b..54aef55f8b90 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -207,6 +207,13 @@ struct ClipRegion final : Op {
SkClipOp op;
void draw(SkCanvas* c, const SkMatrix&) const { c->clipRegion(region, op); }
};
+struct ClipShader final : Op {
+ static const auto kType = Type::ClipShader;
+ ClipShader(const sk_sp<SkShader>& shader, SkClipOp op) : shader(shader), op(op) {}
+ sk_sp<SkShader> shader;
+ SkClipOp op;
+ void draw(SkCanvas* c, const SkMatrix&) const { c->clipShader(shader, op); }
+};
struct ResetClip final : Op {
static const auto kType = Type::ResetClip;
ResetClip() {}
@@ -822,6 +829,9 @@ void DisplayListData::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) {
this->push<ClipRegion>(0, region, op);
}
+void DisplayListData::clipShader(const sk_sp<SkShader>& shader, SkClipOp op) {
+ this->push<ClipShader>(0, shader, op);
+}
void DisplayListData::resetClip() {
this->push<ResetClip>(0);
}
@@ -1134,6 +1144,11 @@ void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
fDL->clipRegion(region, op);
this->INHERITED::onClipRegion(region, op);
}
+void RecordingCanvas::onClipShader(sk_sp<SkShader> shader, SkClipOp op) {
+ setClipMayBeComplex();
+ fDL->clipShader(shader, op);
+ this->INHERITED::onClipShader(shader, op);
+}
void RecordingCanvas::onResetClip() {
// This is part of "replace op" emulation, but rely on the following intersection
// clip to potentially mark the clip as complex. If we are already complex, we do
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index afadbfda7471..965264f31119 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -140,6 +140,7 @@ private:
void translateZ(SkScalar);
void clipPath(const SkPath&, SkClipOp, bool aa);
+ void clipShader(const sk_sp<SkShader>& shader, SkClipOp);
void clipRect(const SkRect&, SkClipOp, bool aa);
void clipRRect(const SkRRect&, SkClipOp, bool aa);
void clipRegion(const SkRegion&, SkClipOp);
@@ -216,6 +217,7 @@ public:
void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
+ void onClipShader(sk_sp<SkShader>, SkClipOp) override;
void onClipRegion(const SkRegion&, SkClipOp) override;
void onResetClip() override;
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index e358b57f6fe1..b1ad8b2eb1b9 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,32 +16,29 @@
#pragma once
-#ifdef __ANDROID__ // Layoutlib does not support device info
-#include "DeviceInfo.h"
-#endif // __ANDROID__
-
-#include "Outline.h"
-#include "Rect.h"
-#include "RevealClip.h"
-#include "effects/StretchEffect.h"
-#include "utils/MathUtils.h"
-#include "utils/PaintUtils.h"
-
#include <SkBlendMode.h>
-#include <SkImageFilter.h>
#include <SkCamera.h>
#include <SkColor.h>
+#include <SkImageFilter.h>
#include <SkMatrix.h>
#include <SkRegion.h>
-
#include <androidfw/ResourceTypes.h>
#include <cutils/compiler.h>
#include <stddef.h>
#include <utils/Log.h>
+
#include <algorithm>
#include <ostream>
#include <vector>
+#include "DeviceInfo.h"
+#include "Outline.h"
+#include "Rect.h"
+#include "RevealClip.h"
+#include "effects/StretchEffect.h"
+#include "utils/MathUtils.h"
+#include "utils/PaintUtils.h"
+
class SkBitmap;
class SkColorFilter;
class SkPaint;
@@ -546,13 +543,9 @@ public:
}
bool fitsOnLayer() const {
-#ifdef __ANDROID__ // Layoutlib does not support device info
const DeviceInfo* deviceInfo = DeviceInfo::get();
return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() &&
mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
-#else
- return mPrimitiveFields.mWidth <= 4096 && mPrimitiveFields.mHeight <= 4096;
-#endif
}
bool promotedToLayer() const {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 008ea3aaa2e7..0b739c361d64 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -70,6 +70,8 @@ public:
: mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
: mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
+ Clip(const sk_sp<SkShader> shader, SkClipOp op, const SkMatrix& m)
+ : mType(Type::Shader), mOp(op), mMatrix(m), mShader(shader) {}
void apply(SkCanvas* canvas) const {
canvas->setMatrix(mMatrix);
@@ -86,6 +88,8 @@ public:
// Ensure path clips are anti-aliased
canvas->clipPath(mPath.value(), mOp, true);
break;
+ case Type::Shader:
+ canvas->clipShader(mShader, mOp);
}
}
@@ -94,6 +98,7 @@ private:
Rect,
RRect,
Path,
+ Shader,
};
Type mType;
@@ -103,6 +108,7 @@ private:
// These are logically a union (tracked separately due to non-POD path).
std::optional<SkPath> mPath;
SkRRect mRRect;
+ sk_sp<SkShader> mShader;
};
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
@@ -341,6 +347,10 @@ void SkiaCanvas::concat(const SkMatrix& matrix) {
mCanvas->concat(matrix);
}
+void SkiaCanvas::concat(const SkM44& matrix) {
+ mCanvas->concat(matrix);
+}
+
void SkiaCanvas::rotate(float degrees) {
mCanvas->rotate(degrees);
}
@@ -409,6 +419,11 @@ bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
return !mCanvas->isClipEmpty();
}
+void SkiaCanvas::clipShader(sk_sp<SkShader> shader, SkClipOp op) {
+ this->recordClip(shader, op);
+ mCanvas->clipShader(shader, op);
+}
+
bool SkiaCanvas::replaceClipRect_deprecated(float left, float top, float right, float bottom) {
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4bf1790e2415..4a012bc601c9 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -86,6 +86,7 @@ public:
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
virtual void concat(const SkMatrix& matrix) override;
+ virtual void concat(const SkM44& matrix) override;
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
virtual void skew(float sx, float sy) override;
@@ -96,6 +97,7 @@ public:
virtual bool quickRejectPath(const SkPath& path) const override;
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
virtual bool clipPath(const SkPath* path, SkClipOp op) override;
+ virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) override;
virtual bool replaceClipRect_deprecated(float left, float top, float right,
float bottom) override;
virtual bool replaceClipPath_deprecated(const SkPath* path) override;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 536ff781badc..af169f4bc4cd 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -16,6 +16,7 @@
#include "VectorDrawable.h"
+#include <gui/TraceUtils.h>
#include <math.h>
#include <string.h>
#include <utils/Log.h>
@@ -26,12 +27,7 @@
#include "SkSamplingOptions.h"
#include "SkScalar.h"
#include "hwui/Paint.h"
-
-#ifdef __ANDROID__
#include "renderthread/RenderThread.h"
-#endif
-
-#include <gui/TraceUtils.h>
#include "utils/Macros.h"
#include "utils/VectorDrawableUtils.h"
@@ -544,7 +540,7 @@ bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
}
bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
- return bitmap && width <= bitmap->width() && height <= bitmap->height();
+ return bitmap && width == bitmap->width() && height == bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index ca119757e816..3d7e559bebe0 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,6 +1,13 @@
package: "com.android.graphics.hwui.flags"
flag {
+ name: "clip_shader"
+ namespace: "core_graphics"
+ description: "API for canvas shader clipping operations"
+ bug: "280116960"
+}
+
+flag {
name: "matrix_44"
namespace: "core_graphics"
description: "API for 4x4 matrix and related canvas functions"
@@ -15,6 +22,20 @@ flag {
}
flag {
+ name: "high_contrast_text_luminance"
+ namespace: "accessibility"
+ description: "Use luminance to determine how to make text more high contrast, instead of RGB heuristic"
+ bug: "186567103"
+}
+
+flag {
+ name: "high_contrast_text_small_text_rect"
+ namespace: "accessibility"
+ description: "Draw a solid rectangle background behind text instead of a stroke outline"
+ bug: "186567103"
+}
+
+flag {
name: "hdr_10bit_plus"
namespace: "core_graphics"
description: "Use 10101010 and FP16 formats for HDR-UI when available"
diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp
index cc79cba5e19c..5e73e7628568 100644
--- a/libs/hwui/apex/android_paint.cpp
+++ b/libs/hwui/apex/android_paint.cpp
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-#include "android/graphics/paint.h"
+#include <SkBlendMode.h>
+#include <SkImageFilter.h>
+#include <hwui/Paint.h>
#include "TypeCast.h"
-
-#include <hwui/Paint.h>
-#include <SkBlendMode.h>
+#include "android/graphics/paint.h"
+#include "include/effects/SkImageFilters.h"
using namespace android;
@@ -43,6 +44,22 @@ static SkBlendMode convertBlendMode(ABlendMode blendMode) {
}
}
+static sk_sp<SkImageFilter> convertImageFilter(AImageFilter imageFilter) {
+ switch (imageFilter) {
+ case AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON:
+ // Material Elevation Level 1 Drop Shadow.
+ sk_sp<SkImageFilter> key_shadow = SkImageFilters::DropShadow(
+ 0.0f, 1.0f, 2.0f, 2.0f, SkColorSetARGB(0x4D, 0x00, 0x00, 0x00), nullptr);
+ sk_sp<SkImageFilter> ambient_shadow = SkImageFilters::DropShadow(
+ 0.0f, 1.0f, 3.0f, 3.0f, SkColorSetARGB(0x26, 0x00, 0x00, 0x00), nullptr);
+ return SkImageFilters::Compose(ambient_shadow, key_shadow);
+ }
+}
+
void APaint_setBlendMode(APaint* paint, ABlendMode blendMode) {
TypeCast::toPaint(paint)->setBlendMode(convertBlendMode(blendMode));
}
+
+void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter) {
+ TypeCast::toPaint(paint)->setImageFilter(convertImageFilter(imageFilter));
+}
diff --git a/libs/hwui/apex/include/android/graphics/paint.h b/libs/hwui/apex/include/android/graphics/paint.h
index 058db8d37619..36b7575d585d 100644
--- a/libs/hwui/apex/include/android/graphics/paint.h
+++ b/libs/hwui/apex/include/android/graphics/paint.h
@@ -26,6 +26,14 @@ __BEGIN_DECLS
*/
typedef struct APaint APaint;
+/**
+ * Predefined Image filter type.
+ */
+enum AImageFilter {
+ /** Drop shadow image filter for PointerIcons. */
+ AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON = 0,
+};
+
/** Bitmap pixel format. */
enum ABlendMode {
/** replaces destination with zero: fully transparent */
@@ -42,6 +50,8 @@ ANDROID_API void APaint_destroyPaint(APaint* paint);
ANDROID_API void APaint_setBlendMode(APaint* paint, ABlendMode blendMode);
+ANDROID_API void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter);
+
__END_DECLS
#ifdef __cplusplus
@@ -54,6 +64,10 @@ namespace graphics {
void setBlendMode(ABlendMode blendMode) { APaint_setBlendMode(mPaint, blendMode); }
+ void setImageFilter(AImageFilter imageFilter) {
+ APaint_setImageFilter(mPaint, imageFilter);
+ }
+
const APaint& get() const { return *mPaint; }
private:
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 8344a86923ee..185436160349 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -147,11 +147,7 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info,
}
sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) {
-#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
- if (bitmap.colorType() == kAlpha_8_SkColorType &&
- !uirenderer::HardwareBitmapUploader::hasAlpha8Support()) {
- return nullptr;
- }
+#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap);
#else
return Bitmap::allocateHeapBitmap(bitmap.info());
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9ec023b2c36f..14b4f584f0f3 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -175,6 +175,7 @@ public:
virtual void setMatrix(const SkMatrix& matrix) = 0;
virtual void concat(const SkMatrix& matrix) = 0;
+ virtual void concat(const SkM44& matrix) = 0;
virtual void rotate(float degrees) = 0;
virtual void scale(float sx, float sy) = 0;
virtual void skew(float sx, float sy) = 0;
@@ -187,6 +188,7 @@ public:
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) = 0;
virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
+ virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) = 0;
// Resets clip to wide open, used to emulate the now-removed SkClipOp::kReplace on
// apps with compatibility < P. Canvases for version P and later are restricted to
// intersect and difference at the Java level, matching SkClipOp's definition.
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 2e6e97634aec..1fcb6920db14 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -16,7 +16,10 @@
#include <SkFontMetrics.h>
#include <SkRRect.h>
+#include <SkTextBlob.h>
+#include <com_android_graphics_hwui_flags.h>
+#include "../utils/Color.h"
#include "Canvas.h"
#include "FeatureFlags.h"
#include "MinikinUtils.h"
@@ -27,8 +30,12 @@
#include "hwui/PaintFilter.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
+namespace flags = com::android::graphics::hwui::flags;
+
namespace android {
+inline constexpr int kHighContrastTextBorderWidth = 4;
+
static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
const Paint& paint, Canvas* canvas) {
const SkScalar strokeWidth = fmax(thickness, 1.0f);
@@ -41,13 +48,24 @@ static void simplifyPaint(int color, Paint* paint) {
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
class DrawTextFunctor {
public:
+ /**
+ * Creates a Functor to draw the given text layout.
+ *
+ * @param layout
+ * @param canvas
+ * @param paint
+ * @param x
+ * @param y
+ * @param totalAdvance
+ * @param bounds bounds of the text. Only required if high contrast text mode is enabled.
+ */
DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
float y, float totalAdvance)
: layout(layout)
@@ -73,15 +91,51 @@ public:
if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
// high contrast draw path
int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
+ bool darken;
+ // This equation should match the one in core/java/android/text/Layout.java
+ if (flags::high_contrast_text_luminance()) {
+ uirenderer::Lab lab = uirenderer::sRGBToLab(color);
+ darken = lab.L <= 50;
+ } else {
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ darken = channelSum < (128 * 3);
+ }
// outline
gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+ if (flags::high_contrast_text_small_text_rect()) {
+ const SkFont& font = paint.getSkFont();
+ auto padding = kHighContrastTextBorderWidth + 0.1f * font.getSize();
+
+ // Draw the background only behind each glyph's bounds. We do this instead of using
+ // the bounds of the entire layout, because the layout includes alignment whitespace
+ // etc which can obscure other text from separate passes (e.g. emojis).
+ // Merge all the glyph bounds into one rect for this line, since drawing a rect for
+ // each glyph is expensive.
+ SkRect glyphBounds;
+ SkRect bgBounds;
+ for (size_t i = start; i < end; i++) {
+ auto glyph = layout.getGlyphId(i);
+
+ font.getBounds(reinterpret_cast<const SkGlyphID*>(&glyph), 1, &glyphBounds,
+ &paint);
+ glyphBounds.offset(layout.getX(i), layout.getY(i));
+
+ bgBounds.join(glyphBounds);
+ }
+
+ if (!bgBounds.isEmpty()) {
+ bgBounds.offset(x, y);
+ bgBounds.outset(padding, padding);
+ canvas->drawRect(bgBounds.fLeft, bgBounds.fTop, bgBounds.fRight,
+ bgBounds.fBottom, outlinePaint);
+ }
+ } else {
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+ }
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index f4ee36ec66d1..bbb142014ed8 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -131,11 +131,6 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
const std::vector<minikin::FontVariation>& variations) const {
SkFontArguments args;
- int ttcIndex;
- std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
- LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
- args.setCollectionIndex(ttcIndex);
std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
skVariation.resize(variations.size());
for (size_t i = 0; i < variations.size(); i++) {
@@ -143,11 +138,10 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
skVariation[i].value = SkFloatToScalar(variations[i].value);
}
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
- sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
+ sk_sp<SkTypeface> face = mTypeface->makeClone(args);
return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
- mFilePath, ttcIndex, variations);
+ mFilePath, mTtcIndex, variations);
}
// hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7552b56d2ad6..d66d7f8e83f4 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
if (mt == nullptr) {
return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
- minikinPaint, startHyphen, endHyphen);
+ minikinPaint, startHyphen, endHyphen, minikinRunFlag);
} else {
return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
}
@@ -96,15 +99,18 @@ void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const
float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
size_t count, size_t bufSize, float* advances,
- minikin::MinikinRect* bounds) {
+ minikin::MinikinRect* bounds, uint32_t* clusterCount) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
const minikin::U16StringPiece textBuf(buf, bufSize);
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances, bounds);
+ endHyphen, advances, bounds, clusterCount, minikinRunFlag);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 61bc881faa54..f8574ee50525 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -53,7 +53,7 @@ public:
static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- float* advances, minikin::MinikinRect* bounds);
+ float* advances, minikin::MinikinRect* bounds, uint32_t* clusterCount);
static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce57bf46..708f96e5a070 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
#include <minikin/FontFamily.h>
#include <minikin/FontFeature.h>
#include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
#include <string>
@@ -144,6 +145,9 @@ public:
bool isDevKern() const { return mDevKern; }
void setDevKern(bool d) { mDevKern = d; }
+ minikin::RunFlag getRunFlag() const { return mRunFlag; }
+ void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
// Deprecated -- bitmapshaders will be taking this flag explicitly
bool isFilterBitmap() const { return mFilterBitmap; }
void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@ private:
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
+ minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f85924..c32ea01db7b7 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@ Paint::Paint(const Paint& paint)
, mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern) {}
-
+ , mDevKern(paint.mDevKern)
+ , mRunFlag(paint.mRunFlag) {}
Paint::~Paint() {}
@@ -68,21 +68,19 @@ Paint& Paint::operator=(const Paint& other) {
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
+ mRunFlag = other.mRunFlag;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
- return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
- a.mFont == b.mFont &&
- a.mLooper == b.mLooper &&
- a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
- a.mFontFeatureSettings == b.mFontFeatureSettings &&
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+ a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+ a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
- a.mFilterBitmap == b.mFilterBitmap &&
- a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
- a.mDevKern == b.mDevKern;
+ a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+ a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
}
void Paint::reset() {
@@ -96,6 +94,7 @@ void Paint::reset() {
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mRunFlag = minikin::RunFlag::NONE;
}
void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@ static const uint32_t sForceAutoHinting = 0x800;
// flags related to minikin::Paint
static const uint32_t sUnderlineFlag = 0x08;
static const uint32_t sStrikeThruFlag = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
// flags no longer supported on native side (but mirrored for compatibility)
static const uint32_t sDevKernFlag = 0x100;
@@ -186,6 +187,12 @@ uint32_t Paint::getJavaFlags() const {
flags |= -(int)mUnderline & sUnderlineFlag;
flags |= -(int)mDevKern & sDevKernFlag;
flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+ if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+ flags |= sTextRunLeftEdge;
+ }
+ if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+ flags |= sTextRunRightEdge;
+ }
return flags;
}
@@ -196,6 +203,15 @@ void Paint::setJavaFlags(uint32_t flags) {
mUnderline = (flags & sUnderlineFlag) != 0;
mDevKern = (flags & sDevKernFlag) != 0;
mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+ std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+ if (flags & sTextRunLeftEdge) {
+ rawFlag |= minikin::RunFlag::LEFT_EDGE;
+ }
+ if (flags & sTextRunRightEdge) {
+ rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+ }
+ mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
}
} // namespace android
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 7cc48661619a..8315c4c0dd4d 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -247,6 +247,9 @@ static jfieldID gFontMetricsInt_descent;
static jfieldID gFontMetricsInt_bottom;
static jfieldID gFontMetricsInt_leading;
+static jclass gRunInfo_class;
+static jfieldID gRunInfo_clusterCount;
+
///////////////////////////////////////////////////////////////////////////////
void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
@@ -511,6 +514,10 @@ int GraphicsJNI::set_metrics_int(JNIEnv* env, jobject metrics, const SkFontMetri
return descent - ascent + leading;
}
+void GraphicsJNI::set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount) {
+ env->SetIntField(runInfo, gRunInfo_clusterCount, clusterCount);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoderWrapper* bitmap) {
@@ -834,5 +841,10 @@ int register_android_graphics_Graphics(JNIEnv* env)
gFontMetricsInt_bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
gFontMetricsInt_leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
+ gRunInfo_class = FindClassOrDie(env, "android/graphics/Paint$RunInfo");
+ gRunInfo_class = MakeGlobalRefOrDie(env, gRunInfo_class);
+
+ gRunInfo_clusterCount = GetFieldIDOrDie(env, gRunInfo_class, "mClusterCount", "I");
+
return 0;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index b9fff36d372e..b0a1074d6693 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -77,6 +77,8 @@ public:
static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*);
static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf);
+ static void set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount);
+
static void set_jpoint(JNIEnv*, jobject jrect, int x, int y);
static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point);
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d84b73d1a1ca..286f06a6bad8 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -114,7 +114,7 @@ namespace PaintGlue {
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
- count, count, advancesArray.get(), nullptr);
+ count, count, advancesArray.get(), nullptr, nullptr);
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -206,7 +206,7 @@ namespace PaintGlue {
}
const float advance = MinikinUtils::measureText(
paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
- contextCount, advancesArray.get(), nullptr);
+ contextCount, advancesArray.get(), nullptr, nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -244,7 +244,7 @@ namespace PaintGlue {
minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
- advancesArray.get(), nullptr);
+ advancesArray.get(), nullptr, nullptr);
size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
start, count, offset, moveOpt);
return static_cast<jint>(result);
@@ -508,7 +508,7 @@ namespace PaintGlue {
static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
const jchar buf[], jint start, jint count, jint bufSize,
jboolean isRtl, jint offset, jfloatArray advances,
- jint advancesIndex, SkRect* drawBounds) {
+ jint advancesIndex, SkRect* drawBounds, uint32_t* clusterCount) {
if (advances) {
size_t advancesLength = env->GetArrayLength(advances);
if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -519,9 +519,9 @@ namespace PaintGlue {
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
minikin::MinikinRect bounds;
if (offset == start + count && advances == nullptr) {
- float result =
- MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
- bufSize, nullptr, drawBounds ? &bounds : nullptr);
+ float result = MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+ bufSize, nullptr,
+ drawBounds ? &bounds : nullptr, clusterCount);
if (drawBounds) {
copyMinikinRectToSkRect(bounds, drawBounds);
}
@@ -529,7 +529,8 @@ namespace PaintGlue {
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get(), drawBounds ? &bounds : nullptr);
+ advancesArray.get(), drawBounds ? &bounds : nullptr,
+ clusterCount);
if (drawBounds) {
copyMinikinRectToSkRect(bounds, drawBounds);
@@ -549,7 +550,7 @@ namespace PaintGlue {
ScopedCharArrayRO textArray(env, text);
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, nullptr, 0, nullptr);
+ isRtl, offset - contextStart, nullptr, 0, nullptr, nullptr);
return result;
}
@@ -558,18 +559,22 @@ namespace PaintGlue {
jint contextStart, jint contextEnd,
jboolean isRtl, jint offset,
jfloatArray advances, jint advancesIndex,
- jobject drawBounds) {
+ jobject drawBounds, jobject runInfo) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
SkRect skDrawBounds;
+ uint32_t clusterCount = 0;
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
isRtl, offset - contextStart, advances, advancesIndex,
- drawBounds ? &skDrawBounds : nullptr);
+ drawBounds ? &skDrawBounds : nullptr, &clusterCount);
if (drawBounds != nullptr) {
GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
}
+ if (runInfo) {
+ GraphicsJNI::set_cluster_count_to_run_info(env, runInfo, clusterCount);
+ }
return result;
}
@@ -578,7 +583,7 @@ namespace PaintGlue {
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get(), nullptr);
+ advancesArray.get(), nullptr, nullptr);
return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
}
@@ -1145,7 +1150,8 @@ static const JNINativeMethod methods[] = {
(void*)PaintGlue::getCharArrayBounds},
{"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
+ {"nGetRunCharacterAdvance",
+ "(J[CIIIIZI[FILandroid/graphics/RectF;Landroid/graphics/Paint$RunInfo;)F",
(void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
{"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba750372d18..9b63a46822ac 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -88,6 +88,10 @@ static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHand
get_canvas(canvasHandle)->setBitmap(bitmap);
}
+static jboolean isHighContrastText(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
+ return get_canvas(canvasHandle)->isHighContrastText() ? JNI_TRUE : JNI_FALSE;
+}
+
static jboolean isOpaque(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
}
@@ -158,6 +162,13 @@ static void concat(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong matrixHan
get_canvas(canvasHandle)->concat(*matrix);
}
+static void concat44(JNIEnv* env, jobject obj, jlong canvasHandle, jfloatArray arr) {
+ jfloat* matVals = env->GetFloatArrayElements(arr, 0);
+ const SkM44 matrix = SkM44::RowMajor(matVals);
+ get_canvas(canvasHandle)->concat(matrix);
+ env->ReleaseFloatArrayElements(arr, matVals, 0);
+}
+
static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
@@ -254,6 +265,23 @@ static jboolean clipPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong pat
return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
+static void clipShader(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong shaderHandle,
+ jint opHandle) {
+ SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle);
+ sk_sp<SkShader> shader = sk_ref_sp(reinterpret_cast<SkShader*>(shaderHandle));
+ switch (rgnOp) {
+ case SkRegion::Op::kIntersect_Op:
+ case SkRegion::Op::kDifference_Op:
+ get_canvas(canvasHandle)->clipShader(shader, static_cast<SkClipOp>(rgnOp));
+ break;
+ default:
+ ALOGW("Ignoring unsupported clip operation %d", opHandle);
+ SkRect clipBounds; // ignored
+ get_canvas(canvasHandle)->getClipBounds(&clipBounds);
+ break;
+ }
+}
+
static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
get_canvas(canvasHandle)->drawColor(color, mode);
@@ -613,6 +641,12 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO text(env, charArray);
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + index, count, // text buffer
@@ -620,6 +654,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +664,12 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
const int count = end - start;
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + start, count, // text buffer
@@ -636,6 +677,7 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +723,15 @@ static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharA
jchar* jchars = env->GetCharArrayElements(text, NULL);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseCharArrayElements(text, jchars, 0);
}
@@ -697,9 +745,15 @@ static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstri
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
*path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseStringChars(text, jchars);
}
@@ -730,40 +784,43 @@ static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
- {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
- {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
- {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
- {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
-
- // ------------ @FastNative ----------------
- {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster},
- {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap},
- {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
-
- // ------------ @CriticalNative ----------------
- {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
- {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
- {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
- {"nSave","(JI)I", (void*) CanvasJNI::save},
- {"nSaveLayer","(JFFFFJ)I", (void*) CanvasJNI::saveLayer},
- {"nSaveLayerAlpha","(JFFFFI)I", (void*) CanvasJNI::saveLayerAlpha},
- {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer},
- {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer},
- {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
- {"nRestore","(J)Z", (void*) CanvasJNI::restore},
- {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
- {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
- {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
- {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
- {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
- {"nScale","(JFF)V", (void*) CanvasJNI::scale},
- {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
- {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
- {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
- {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
- {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
- {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
- {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
+ {"nGetNativeFinalizer", "()J", (void*)CanvasJNI::getNativeFinalizer},
+ {"nFreeCaches", "()V", (void*)CanvasJNI::freeCaches},
+ {"nFreeTextLayoutCaches", "()V", (void*)CanvasJNI::freeTextLayoutCaches},
+ {"nSetCompatibilityVersion", "(I)V", (void*)CanvasJNI::setCompatibilityVersion},
+
+ // ------------ @FastNative ----------------
+ {"nInitRaster", "(J)J", (void*)CanvasJNI::initRaster},
+ {"nSetBitmap", "(JJ)V", (void*)CanvasJNI::setBitmap},
+ {"nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*)CanvasJNI::getClipBounds},
+
+ // ------------ @CriticalNative ----------------
+ {"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+ {"nIsHighContrastText", "(J)Z", (void*)CanvasJNI::isHighContrastText},
+ {"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
+ {"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
+ {"nSave", "(JI)I", (void*)CanvasJNI::save},
+ {"nSaveLayer", "(JFFFFJ)I", (void*)CanvasJNI::saveLayer},
+ {"nSaveLayerAlpha", "(JFFFFI)I", (void*)CanvasJNI::saveLayerAlpha},
+ {"nSaveUnclippedLayer", "(JIIII)I", (void*)CanvasJNI::saveUnclippedLayer},
+ {"nRestoreUnclippedLayer", "(JIJ)V", (void*)CanvasJNI::restoreUnclippedLayer},
+ {"nGetSaveCount", "(J)I", (void*)CanvasJNI::getSaveCount},
+ {"nRestore", "(J)Z", (void*)CanvasJNI::restore},
+ {"nRestoreToCount", "(JI)V", (void*)CanvasJNI::restoreToCount},
+ {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
+ {"nSetMatrix", "(JJ)V", (void*)CanvasJNI::setMatrix},
+ {"nConcat", "(JJ)V", (void*)CanvasJNI::concat},
+ {"nConcat", "(J[F)V", (void*)CanvasJNI::concat44},
+ {"nRotate", "(JF)V", (void*)CanvasJNI::rotate},
+ {"nScale", "(JFF)V", (void*)CanvasJNI::scale},
+ {"nSkew", "(JFF)V", (void*)CanvasJNI::skew},
+ {"nTranslate", "(JFF)V", (void*)CanvasJNI::translate},
+ {"nQuickReject", "(JJ)Z", (void*)CanvasJNI::quickRejectPath},
+ {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+ {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
+ {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+ {"nClipShader", "(JJI)V", (void*)CanvasJNI::clipShader},
+ {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
};
// If called from Canvas these are regular JNI
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index fdb237387098..d03ceb471d6c 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -32,6 +32,7 @@ LIBHWUI { # platform-only /* HWUI isn't current a module, so all of these are st
APaint_createPaint;
APaint_destroyPaint;
APaint_setBlendMode;
+ APaint_setImageFilter;
ARegionIterator_acquireIterator;
ARegionIterator_releaseIterator;
ARegionIterator_isComplex;
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
index 234f42d79cb7..756b937f7de3 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -20,6 +20,8 @@
#include <cstring>
+#include "GrDirectContext.h"
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -114,8 +116,16 @@ void ATraceMemoryDump::startFrame() {
/**
* logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ *
+ * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
+ * with this tracer, false otherwise. Leaving this false allows this function to quickly query total
+ * and purgable GPU memory without the caller having to spend time in
+ * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
+ * cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
+ * which may be a loss of granularity depending on the GPU backend and the categories defined in
+ * sResourceMap.
*/
-void ATraceMemoryDump::logTraces() {
+void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
// Accumulate data from last dumpName
recordAndResetCountersIfNeeded("");
uint64_t hwui_all_frame_memory = 0;
@@ -126,6 +136,20 @@ void ATraceMemoryDump::logTraces() {
ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
}
}
+
+ if (!gpuMemoryIsAlreadyInDump && grContext) {
+ // Total GPU memory
+ int gpuResourceCount;
+ size_t gpuResourceBytes;
+ grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
+ hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
+ ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
+
+ // Purgable subset of GPU memory
+ size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
+ ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
+ }
+
ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
}
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
index 4592711dd5b5..777d1a2ddb5b 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.h
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -16,6 +16,7 @@
#pragma once
+#include <GrDirectContext.h>
#include <SkString.h>
#include <SkTraceMemoryDump.h>
@@ -50,7 +51,7 @@ public:
void startFrame();
- void logTraces();
+ void logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext);
private:
std::string mLastDumpName;
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 6a052dbb7cea..260547cda1c2 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -90,11 +90,6 @@ protected:
mOutput << mIdent << "drawTextBlob" << std::endl;
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
- mOutput << mIdent << "drawImage" << std::endl;
- }
-
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SrcRectConstraint) override {
mOutput << mIdent << "drawImageRect" << std::endl;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index e0f1f6ef44be..326b6ed77fe0 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,9 +650,14 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
break;
case ColorMode::Hdr:
- mSurfaceColorType = SkColorType::kN32_SkColorType;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(
- GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorType = SkColorType::kN32_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
break;
case ColorMode::Hdr10:
mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
@@ -669,8 +674,13 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
- SkNamedGamut::kDisplayP3);
+
+ if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
} else {
mTargetSdrHdrRatio = 1.f;
}
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/platform/android/thread/ThreadBase.h
index 0289d3fd2ef7..2f3581f8b355 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/platform/android/thread/ThreadBase.h
@@ -17,14 +17,14 @@
#ifndef HWUI_THREADBASE_H
#define HWUI_THREADBASE_H
-#include "WorkQueue.h"
-#include "utils/Macros.h"
-
#include <utils/Looper.h>
#include <utils/Thread.h>
#include <algorithm>
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
namespace android::uirenderer {
class ThreadBase : public Thread {
diff --git a/libs/hwui/platform/host/ProfileDataContainer.cpp b/libs/hwui/platform/host/ProfileDataContainer.cpp
new file mode 100644
index 000000000000..9ed1b02a8082
--- /dev/null
+++ b/libs/hwui/platform/host/ProfileDataContainer.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileDataContainer.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace uirenderer {
+
+void ProfileDataContainer::freeData() REQUIRES(mJankDataMutex) {
+ delete mData;
+ mIsMapped = false;
+ mData = nullptr;
+}
+
+void ProfileDataContainer::rotateStorage() {
+ std::lock_guard lock(mJankDataMutex);
+ mData->reset();
+}
+
+void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+ ALOGE("Ashmem is not supported for non-Android configurations");
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/Readback.cpp b/libs/hwui/platform/host/Readback.cpp
new file mode 100644
index 000000000000..b024ec02efc3
--- /dev/null
+++ b/libs/hwui/platform/host/Readback.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Readback.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) {
+}
+
+CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
+ SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
+ SkBitmap* bitmap) {
+ return false;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
new file mode 100644
index 000000000000..1d16655bf73c
--- /dev/null
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebViewFunctorManager.h"
+
+namespace android::uirenderer {
+
+WebViewFunctor::WebViewFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode)
+ : mData(data) {}
+
+WebViewFunctor::~WebViewFunctor() {}
+
+void WebViewFunctor::sync(const WebViewSyncData& syncData) const {}
+
+void WebViewFunctor::onRemovedFromTree() {}
+
+bool WebViewFunctor::prepareRootSurfaceControl() {
+ return true;
+}
+
+void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {}
+
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {}
+
+void WebViewFunctor::postDrawVk() {}
+
+void WebViewFunctor::destroyContext() {}
+
+void WebViewFunctor::removeOverlays() {}
+
+ASurfaceControl* WebViewFunctor::getSurfaceControl() {
+ return mSurfaceControl;
+}
+
+void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
+
+void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {}
+
+WebViewFunctorManager& WebViewFunctorManager::instance() {
+ static WebViewFunctorManager sInstance;
+ return sInstance;
+}
+
+int WebViewFunctorManager::createFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode) {
+ return 0;
+}
+
+void WebViewFunctorManager::releaseFunctor(int functor) {}
+
+void WebViewFunctorManager::onContextDestroyed() {}
+
+void WebViewFunctorManager::destroyFunctor(int functor) {}
+
+sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
+ return nullptr;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/platform/host/renderthread/CacheManager.cpp b/libs/hwui/platform/host/renderthread/CacheManager.cpp
new file mode 100644
index 000000000000..b03f400cd7d6
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/CacheManager.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "renderthread/CacheManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {}
+
+void CacheManager::setupCacheLimits() {}
+
+void CacheManager::destroy() {}
+
+void CacheManager::trimMemory(TrimLevel mode) {}
+
+void CacheManager::trimCaches(CacheTrimLevel mode) {}
+
+void CacheManager::trimStaleResources() {}
+
+void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {}
+
+void CacheManager::onFrameCompleted() {}
+
+void CacheManager::onThreadIdle() {}
+
+void CacheManager::scheduleDestroyContext() {}
+
+void CacheManager::cancelDestroyContext() {}
+
+bool CacheManager::areAllContextsStopped() {
+ return false;
+}
+
+void CacheManager::checkUiHidden() {}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {}
+
+void CacheManager::onContextStopped(CanvasContext* context) {}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp
new file mode 100644
index 000000000000..6f08b5979772
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "renderthread/RenderThread.h"
+
+#include "Readback.h"
+#include "renderthread/VulkanManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+static bool gHasRenderThreadInstance = false;
+static JVMAttachHook gOnStartHook = nullptr;
+
+ASurfaceControlFunctions::ASurfaceControlFunctions() {}
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+void RenderThread::setOnStartHook(JVMAttachHook onStartHook) {
+ LOG_ALWAYS_FATAL_IF(hasInstance(), "can't set an onStartHook after we've started...");
+ gOnStartHook = onStartHook;
+}
+
+JVMAttachHook RenderThread::getOnStartHook() {
+ return gOnStartHook;
+}
+
+RenderThread& RenderThread::getInstance() {
+ [[clang::no_destroy]] static sp<RenderThread> sInstance = []() {
+ sp<RenderThread> thread = sp<RenderThread>::make();
+ thread->start("RenderThread");
+ return thread;
+ }();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread()
+ : ThreadBase()
+ , mVsyncSource(nullptr)
+ , mVsyncRequested(false)
+ , mFrameCallbackTaskPending(false)
+ , mRenderState(nullptr)
+ , mEglManager(nullptr)
+ , mFunctorManager(WebViewFunctorManager::instance())
+ , mGlobalProfileData(mJankDataMutex) {
+ Properties::load();
+}
+
+RenderThread::~RenderThread() {}
+
+void RenderThread::initThreadLocals() {
+ mCacheManager = new CacheManager(*this);
+}
+
+void RenderThread::requireGlContext() {}
+
+void RenderThread::requireVkContext() {}
+
+void RenderThread::initGrContextOptions(GrContextOptions& options) {}
+
+void RenderThread::destroyRenderingContext() {}
+
+VulkanManager& RenderThread::vulkanManager() {
+ return *mVkManager;
+}
+
+void RenderThread::dumpGraphicsMemory(int fd, bool includeProfileData) {}
+
+void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+Readback& RenderThread::readback() {
+ if (!mReadback) {
+ mReadback = new Readback(*this);
+ }
+
+ return *mReadback;
+}
+
+void RenderThread::setGrContext(sk_sp<GrDirectContext> context) {}
+
+sk_sp<GrDirectContext> RenderThread::requireGrContext() {
+ return mGrContext;
+}
+
+bool RenderThread::threadLoop() {
+ if (gOnStartHook) {
+ gOnStartHook("RenderThread");
+ }
+ initThreadLocals();
+
+ while (true) {
+ waitForWork();
+ processQueue();
+ mCacheManager->onThreadIdle();
+ }
+
+ return false;
+}
+
+void RenderThread::postFrameCallback(IFrameCallback* callback) {}
+
+bool RenderThread::removeFrameCallback(IFrameCallback* callback) {
+ return false;
+}
+
+void RenderThread::pushBackFrameCallback(IFrameCallback* callback) {}
+
+sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
+ return nullptr;
+}
+
+bool RenderThread::isCurrent() {
+ return true;
+}
+
+void RenderThread::preload() {}
+
+void RenderThread::trimMemory(TrimLevel level) {}
+
+void RenderThread::trimCaches(CacheTrimLevel level) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
new file mode 100644
index 000000000000..d709430cc9b6
--- /dev/null
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HWUI_THREADBASE_H
+#define HWUI_THREADBASE_H
+
+#include <utils/Thread.h>
+
+#include <algorithm>
+
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
+namespace android::uirenderer {
+
+class ThreadBase : public Thread {
+ PREVENT_COPY_AND_ASSIGN(ThreadBase);
+
+public:
+ ThreadBase() : Thread(false), mQueue([this]() { mCondition.notify_all(); }, mLock) {}
+
+ WorkQueue& queue() { return mQueue; }
+
+ void requestExit() { Thread::requestExit(); }
+
+ void start(const char* name = "ThreadBase") { Thread::run(name); }
+
+ void join() { Thread::join(); }
+
+ bool isRunning() const { return Thread::isRunning(); }
+
+protected:
+ void waitForWork() {
+ std::unique_lock lock{mLock};
+ nsecs_t nextWakeup = mQueue.nextWakeup(lock);
+ std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
+ if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
+ int timeout = nextWakeup - WorkQueue::clock::now();
+ if (timeout < 0) timeout = 0;
+ duration = std::chrono::nanoseconds(timeout);
+ }
+ mCondition.wait_for(lock, duration);
+ }
+
+ void processQueue() { mQueue.process(); }
+
+ virtual bool threadLoop() override {
+ while (!exitPending()) {
+ waitForWork();
+ processQueue();
+ }
+ return false;
+ }
+
+private:
+ WorkQueue mQueue;
+ std::mutex mLock;
+ std::condition_variable mCondition;
+};
+
+} // namespace android::uirenderer
+
+#endif // HWUI_THREADBASE_H
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index 22ae59e5137b..493c943079ab 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -17,15 +17,7 @@
#ifndef FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
-#ifdef __ANDROID__ // Layoutlib does not support surface control
#include <android/surface_control.h>
-#else
-// To avoid ifdefs around overlay implementation all over the place we typedef these to void *. They
-// won't be used.
-typedef void* ASurfaceControl;
-typedef void* ASurfaceTransaction;
-#endif
-
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
#include <private/hwui/DrawVkInfo.h>
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 30d461271c89..ac2a9366a1f6 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@ void CacheManager::trimMemory(TrimLevel mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(GrSyncCpu::kYes);
- switch (mode) {
- case TrimLevel::BACKGROUND:
- mGrContext->freeGpuResources();
- SkGraphics::PurgeAllCaches();
- mRenderThread.destroyRenderingContext();
- break;
- case TrimLevel::UI_HIDDEN:
- // Here we purge all the unlocked scratch resources and then toggle the resources cache
- // limits between the background and max amounts. This causes the unlocked resources
- // that have persistent data to be purged in LRU order.
- mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
- SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
- break;
- default:
- break;
+ if (mode >= TrimLevel::BACKGROUND) {
+ mGrContext->freeGpuResources();
+ SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
+ } else if (mode == TrimLevel::UI_HIDDEN) {
+ // Here we purge all the unlocked scratch resources and then toggle the resources cache
+ // limits between the background and max amounts. This causes the unlocked resources
+ // that have persistent data to be purged in LRU order.
+ mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+ SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
}
}
@@ -269,13 +264,14 @@ void CacheManager::onFrameCompleted() {
cancelDestroyContext();
mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
+ ATRACE_NAME("dumpingMemoryStatistics");
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
SkGraphics::DumpMemoryStatistics(&tracer);
- if (mGrContext) {
+ if (mGrContext && Properties::debugTraceGpuResourceCategories) {
mGrContext->dumpMemoryStatistics(&tracer);
}
- tracer.logTraces();
+ tracer.logTraces(Properties::debugTraceGpuResourceCategories, mGrContext.get());
}
}
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 94f35fd9eaf2..2904dfe76f40 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -37,6 +37,9 @@
// Android-specific addition that is used to show when frames began in systrace
EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
+static constexpr auto P3_XRB = static_cast<android_dataspace>(
+ ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB | ADATASPACE_RANGE_EXTENDED);
+
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -438,22 +441,32 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
colorMode = ColorMode::Default;
}
- if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+ // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
+ const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+ DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+ if (canUseFp16) {
if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
colorMode = ColorMode::Default;
} else {
config = mEglConfigF16;
}
}
+
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
switch (colorMode) {
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
+ case ColorMode::Hdr:
+ if (canUseFp16) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ break;
+ // No fp16 support so fallthrough to HDR10
+ }
// We don't have an EGL colorspace for extended range P3 that's used for HDR
// So override it after configuring the EGL context
- case ColorMode::Hdr:
case ColorMode::Hdr10:
overrideWindowDataSpaceForHdr = true;
attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
@@ -497,9 +510,7 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
// This relies on knowing that EGL will not re-set the dataspace after the call to
// eglCreateWindowSurface. Since the handling of the colorspace extension is largely
// implemented in libEGL in the platform, we can safely assume this is the case
- int32_t err = ANativeWindow_setBuffersDataSpace(
- window,
- static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED));
+ int32_t err = ANativeWindow_setBuffersDataSpace(window, P3_XRB);
LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err);
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 623ee4e6c27e..a024aeb285f9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,11 +17,12 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
-#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <private/android/choreographer.h>
#include <sys/resource.h>
#include <ui/FatVector.h>
#include <utils/Condition.h>
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 79e57de9d66f..045d26f1d329 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -20,10 +20,7 @@
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
-#include <private/android/choreographer.h>
#include <surface_control_private.h>
-#include <thread/ThreadBase.h>
-#include <utils/Looper.h>
#include <utils/Thread.h>
#include <memory>
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d469d0..b5f7caaf1b5b 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <sstream>
+
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@ static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+ VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
+ if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+ new VkPhysicalDeviceFaultFeaturesEXT;
+ deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ deviceFaultFeatures->pNext = nullptr;
+ *tailPNext = deviceFaultFeatures;
+ tailPNext = &deviceFaultFeatures->pNext;
+ }
+
// query to get the physical device features
mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
// this looks like it would slow things down,
@@ -405,6 +418,79 @@ void VulkanManager::initialize() {
});
}
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":"
+ << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":"
+ << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+} // anonymous namespace
+
static void onGrContextReleased(void* context) {
VulkanManager* manager = (VulkanManager*)context;
manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
+ backendContext.fDeviceLostContext = nullptr;
+ backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+ ? deviceLostProcRenderThread
+ : deviceLostProcUploadThread;
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 20b743bab2c2..a8e85475aff0 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -29,6 +29,9 @@ namespace android {
namespace uirenderer {
namespace renderthread {
+static constexpr auto P3_XRB = static_cast<android_dataspace>(
+ ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB | ADATASPACE_RANGE_EXTENDED);
+
static int InvertTransform(int transform) {
switch (transform) {
case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
@@ -214,8 +217,7 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
outWindowInfo->colorMode = colorMode;
if (colorMode == ColorMode::Hdr || colorMode == ColorMode::Hdr10) {
- outWindowInfo->dataspace =
- static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
+ outWindowInfo->dataspace = P3_XRB;
} else {
outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
}
@@ -541,8 +543,7 @@ void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) {
}
if (mWindowInfo.colorMode == ColorMode::Hdr || mWindowInfo.colorMode == ColorMode::Hdr10) {
- mWindowInfo.dataspace =
- static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
+ mWindowInfo.dataspace = P3_XRB;
} else {
mWindowInfo.dataspace = ColorSpaceToADataSpace(
mWindowInfo.colorspace.get(), BufferFormatToColorType(mWindowInfo.bufferFormat));
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index dc36a2e01815..df5f04f9904e 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -109,12 +109,6 @@ public:
drawPoints++;
}
- int drawImageCount = 0;
- void onDrawImage2(const SkImage* image, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint* paint) override {
- drawImageCount++;
- }
-
int drawImageRectCount = 0;
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SkCanvas::SrcRectConstraint) override {
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 18c50472a7df..4ae76e2f1fd2 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -492,7 +492,7 @@ TEST(CanvasOp, simpleDrawImage) {
CallCountingCanvas canvas;
EXPECT_EQ(0, canvas.sumTotalDrawCalls());
rasterizeCanvasBuffer(buffer, &canvas);
- EXPECT_EQ(1, canvas.drawImageCount);
+ EXPECT_EQ(1, canvas.drawImageRectCount);
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 96a0c6114682..8b95e0cd267d 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -69,10 +69,6 @@ public:
void onDrawPath(const SkPath&, const SkPaint&) {
ADD_FAILURE() << "onDrawPath not expected in this test";
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) {
- ADD_FAILURE() << "onDrawImage not expected in this test";
- }
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SrcRectConstraint) {
ADD_FAILURE() << "onDrawImageRect not expected in this test";
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 073a8357e574..ca540874833c 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -941,8 +941,9 @@ RENDERTHREAD_TEST(RenderNodeDrawable, simple) {
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(0, mDrawCounter++);
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(1, mDrawCounter++);
}
};
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 3ded540c3152..785e2869d15e 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -303,8 +303,9 @@ RENDERTHREAD_TEST(SkiaPipeline, clipped) {
class ClippedTestCanvas : public SkCanvas {
public:
ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(0, mDrawCounter++);
EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(this));
EXPECT_TRUE(getTotalMatrix().isIdentity());
@@ -338,8 +339,9 @@ RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
class ClippedTestCanvas : public SkCanvas {
public:
ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(0, mDrawCounter++);
// Expect clip to be rotated.
EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft,
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
index 28538c4b7a7b..ecfe41f39ecb 100644
--- a/libs/hwui/utils/ForceDark.h
+++ b/libs/hwui/utils/ForceDark.h
@@ -17,6 +17,8 @@
#ifndef FORCEDARKUTILS_H
#define FORCEDARKUTILS_H
+#include <stdint.h>
+
namespace android {
namespace uirenderer {
@@ -26,9 +28,9 @@ namespace uirenderer {
* This should stay in sync with the java @IntDef in
* frameworks/base/graphics/java/android/graphics/ForceDarkType.java
*/
-enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+enum class ForceDarkType : uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
} /* namespace uirenderer */
} /* namespace android */
-#endif // FORCEDARKUTILS_H \ No newline at end of file
+#endif // FORCEDARKUTILS_H
diff --git a/libs/hwui/utils/HostColorSpace.cpp b/libs/hwui/utils/HostColorSpace.cpp
deleted file mode 100644
index 77a6820c6999..000000000000
--- a/libs/hwui/utils/HostColorSpace.cpp
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// This is copied from framework/native/libs/ui in order not to include libui in host build
-
-#include <ui/ColorSpace.h>
-
-using namespace std::placeholders;
-
-namespace android {
-
-static constexpr float linearResponse(float v) {
- return v;
-}
-
-static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c;
-}
-
-static constexpr float response(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x;
-}
-
-static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c;
-}
-
-static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f;
-}
-
-static float absRcpResponse(float x, float g,float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x);
-}
-
-static float absResponse(float x, float g, float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x);
-}
-
-static float safePow(float x, float e) {
- return powf(x < 0.0f ? 0.0f : x, e);
-}
-
-static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(rcpResponse, _1, parameters);
- }
- return std::bind(rcpFullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(response, _1, parameters);
- }
- return std::bind(fullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toOETF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, 1.0f / gamma);
-}
-
-static ColorSpace::transfer_function toEOTF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, gamma);
-}
-
-static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) {
- float3 r(rgbToXYZ * float3{1, 0, 0});
- float3 g(rgbToXYZ * float3{0, 1, 0});
- float3 b(rgbToXYZ * float3{0, 0, 1});
-
- return {{r.xy / dot(r, float3{1}),
- g.xy / dot(g, float3{1}),
- b.xy / dot(b, float3{1})}};
-}
-
-static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
- float3 w(rgbToXYZ * float3{1});
- return w.xy / dot(w, float3{1});
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-constexpr mat3 ColorSpace::computeXYZMatrix(
- const std::array<float2, 3>& primaries, const float2& whitePoint) {
- const float2& R = primaries[0];
- const float2& G = primaries[1];
- const float2& B = primaries[2];
- const float2& W = whitePoint;
-
- float oneRxRy = (1 - R.x) / R.y;
- float oneGxGy = (1 - G.x) / G.y;
- float oneBxBy = (1 - B.x) / B.y;
- float oneWxWy = (1 - W.x) / W.y;
-
- float RxRy = R.x / R.y;
- float GxGy = G.x / G.y;
- float BxBy = B.x / B.y;
- float WxWy = W.x / W.y;
-
- float BY =
- ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
- ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
- float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
- float RY = 1 - GY - BY;
-
- float RYRy = RY / R.y;
- float GYGy = GY / G.y;
- float BYBy = BY / B.y;
-
- return {
- float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)},
- float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)},
- float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)}
- };
-}
-
-const ColorSpace ColorSpace::sRGB() {
- return {
- "sRGB IEC61966-2.1",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::linearSRGB() {
- return {
- "sRGB IEC61966-2.1 (Linear)",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f}
- };
-}
-
-const ColorSpace ColorSpace::extendedSRGB() {
- return {
- "scRGB-nl IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(clamp<float>, _1, -0.799f, 2.399f)
- };
-}
-
-const ColorSpace ColorSpace::linearExtendedSRGB() {
- return {
- "scRGB IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- 1.0f,
- std::bind(clamp<float>, _1, -0.5f, 7.499f)
- };
-}
-
-const ColorSpace ColorSpace::NTSC() {
- return {
- "NTSC (1953)",
- {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}},
- {0.310f, 0.316f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT709() {
- return {
- "Rec. ITU-R BT.709-5",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT2020() {
- return {
- "Rec. ITU-R BT.2020-1",
- {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::AdobeRGB() {
- return {
- "Adobe RGB (1998)",
- {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}},
- {0.3127f, 0.3290f},
- 2.2f
- };
-}
-
-const ColorSpace ColorSpace::ProPhotoRGB() {
- return {
- "ROMM RGB ISO 22028-2:2013",
- {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}},
- {0.34567f, 0.35850f},
- {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DisplayP3() {
- return {
- "Display P3",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DCIP3() {
- return {
- "SMPTE RP 431-2-2007 DCI (P3)",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.314f, 0.351f},
- 2.6f
- };
-}
-
-const ColorSpace ColorSpace::ACES() {
- return {
- "SMPTE ST 2065-1:2012 ACES",
- {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-const ColorSpace ColorSpace::ACEScg() {
- return {
- "Academy S-2014-004 ACEScg",
- {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src,
- const ColorSpace& dst) {
- size = clamp(size, 2u, 256u);
- float m = 1.0f / float(size - 1);
-
- std::unique_ptr<float3[]> lut(new float3[size * size * size]);
- float3* data = lut.get();
-
- ColorSpaceConnector connector(src, dst);
-
- for (uint32_t z = 0; z < size; z++) {
- for (int32_t y = int32_t(size - 1); y >= 0; y--) {
- for (uint32_t x = 0; x < size; x++) {
- *data++ = connector.transform({x * m, y * m, z * m});
- }
- }
- }
-
- return lut;
-}
-
-static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
-static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
-static const mat3 BRADFORD = mat3{
- float3{ 0.8951f, -0.7502f, 0.0389f},
- float3{ 0.2664f, 1.7135f, -0.0685f},
- float3{-0.1614f, 0.0367f, 1.0296f}
-};
-
-static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
- float3 srcLMS = matrix * srcWhitePoint;
- float3 dstLMS = matrix * dstWhitePoint;
- return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
-}
-
-ColorSpaceConnector::ColorSpaceConnector(
- const ColorSpace& src,
- const ColorSpace& dst) noexcept
- : mSource(src)
- , mDestination(dst) {
-
- if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) {
- mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ();
- } else {
- mat3 rgbToXYZ(src.getRGBtoXYZ());
- mat3 xyzToRGB(dst.getXYZtoRGB());
-
- float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1});
- float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1});
-
- if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
- }
-
- if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
- }
-
- mTransform = xyzToRGB * rgbToXYZ;
- }
-}
-
-}; // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index bba9c9764eee..f9dc5fac7e21 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -41,6 +41,8 @@ namespace android {
namespace {
+static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+
const ui::Transform kIdentityTransform;
} // namespace
@@ -111,7 +113,11 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
: PointerController(
policy, looper, spriteController, enabled,
[](const sp<android::gui::WindowInfosListener>& listener) {
- SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
+ auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+ std::vector<android::gui::DisplayInfo>{});
+ SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
+ &initialInfo);
+ return initialInfo.second;
},
[](const sp<android::gui::WindowInfosListener>& listener) {
SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
@@ -119,8 +125,9 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper, SpriteController& spriteController,
- bool enabled, WindowListenerConsumer registerListener,
- WindowListenerConsumer unregisterListener)
+ bool enabled,
+ const WindowListenerRegisterConsumer& registerListener,
+ WindowListenerUnregisterConsumer unregisterListener)
: mEnabled(enabled),
mContext(policy, looper, spriteController, *this),
mCursorController(mContext),
@@ -128,7 +135,8 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
mUnregisterWindowInfosListener(std::move(unregisterListener)) {
std::scoped_lock lock(getLock());
mLocked.presentation = Presentation::SPOT;
- registerListener(mDisplayInfoListener);
+ const auto& initialDisplayInfos = registerListener(mDisplayInfoListener);
+ onDisplayInfosChangedLocked(initialDisplayInfos);
}
PointerController::~PointerController() {
@@ -218,7 +226,7 @@ void PointerController::setPresentation(Presentation presentation) {
mLocked.presentation = presentation;
- if (input_flags::enable_pointer_choreographer()) {
+ if (ENABLE_POINTER_CHOREOGRAPHER) {
// When pointer choreographer is enabled, the presentation mode is only set once when the
// PointerController is constructed, before the display viewport is provided.
// TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index a8b963367f4c..6ee5707622ca 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -79,14 +79,16 @@ public:
std::string dump() override;
protected:
- using WindowListenerConsumer =
+ using WindowListenerRegisterConsumer = std::function<std::vector<gui::DisplayInfo>(
+ const sp<android::gui::WindowInfosListener>&)>;
+ using WindowListenerUnregisterConsumer =
std::function<void(const sp<android::gui::WindowInfosListener>&)>;
// Constructor used to test WindowInfosListener registration.
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
SpriteController& spriteController, bool enabled,
- WindowListenerConsumer registerListener,
- WindowListenerConsumer unregisterListener);
+ const WindowListenerRegisterConsumer& registerListener,
+ WindowListenerUnregisterConsumer unregisterListener);
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
SpriteController& spriteController, bool enabled);
@@ -129,7 +131,7 @@ private:
};
sp<DisplayInfoListener> mDisplayInfoListener;
- const WindowListenerConsumer mUnregisterWindowInfosListener;
+ const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
diff --git a/libs/input/SpriteIcon.cpp b/libs/input/SpriteIcon.cpp
index b7e51e22a214..59e36e4b0d1e 100644
--- a/libs/input/SpriteIcon.cpp
+++ b/libs/input/SpriteIcon.cpp
@@ -34,6 +34,9 @@ bool SpriteIcon::draw(sp<Surface> surface) const {
graphics::Paint paint;
paint.setBlendMode(ABLEND_MODE_SRC);
+ if (drawNativeDropShadow) {
+ paint.setImageFilter(AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON);
+ }
graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace());
canvas.drawBitmap(bitmap, 0, 0, &paint);
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 5f085bbd2374..0939af46c258 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -27,26 +27,20 @@ namespace android {
* Icon that a sprite displays, including its hotspot.
*/
struct SpriteIcon {
- inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {}
- inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
- float hotSpotY)
- : bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) {}
-
- graphics::Bitmap bitmap;
- PointerIconStyle style;
- float hotSpotX;
- float hotSpotY;
-
- inline SpriteIcon copy() const {
- return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY);
- }
-
- inline void reset() {
- bitmap.reset();
- style = PointerIconStyle::TYPE_NULL;
- hotSpotX = 0;
- hotSpotY = 0;
- }
+ explicit SpriteIcon() = default;
+ explicit SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
+ float hotSpotY, bool drawNativeDropShadow)
+ : bitmap(bitmap),
+ style(style),
+ hotSpotX(hotSpotX),
+ hotSpotY(hotSpotY),
+ drawNativeDropShadow(drawNativeDropShadow) {}
+
+ graphics::Bitmap bitmap{};
+ PointerIconStyle style{PointerIconStyle::TYPE_NULL};
+ float hotSpotX{};
+ float hotSpotY{};
+ bool drawNativeDropShadow{false};
inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index b8de919fbd8c..99952aa14904 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -93,7 +93,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32
const PointerCoords& c = spotCoords[spotIdToIndex[id]];
ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
- c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId);
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
}
#endif
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index adfa91e96ebb..a1bb5b3f1cc4 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -160,9 +160,11 @@ public:
: PointerController(
policy, looper, spriteController,
/*enabled=*/true,
- [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ [&registeredListener](const sp<android::gui::WindowInfosListener>& listener)
+ -> std::vector<gui::DisplayInfo> {
// Register listener
registeredListener = listener;
+ return {};
},
[&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
// Unregister listener