summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java30
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java36
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java36
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java128
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java14
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java9
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java50
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java66
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java6
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java84
-rw-r--r--libs/WindowManager/Shell/Android.bp15
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig17
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp95
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml35
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml24
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml35
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS4
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.pngbin0 -> 56623 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.pngbin0 -> 56623 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.pngbin0 -> 56741 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.pngbin0 -> 56741 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties3
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt58
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt53
l---------libs/WindowManager/Shell/multivalentScreenshotTestsForDevice1
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml146
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml26
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml32
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt154
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java)39
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt221
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java69
-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.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java47
-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/DropZoneView.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java2
-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/PinnedStackListenerForwarder.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java134
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java2
-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.java47
-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/GroupedRecentTaskInfo.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java179
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java194
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt225
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp84
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java149
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt478
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt107
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt590
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt87
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt456
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java207
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt132
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java218
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt78
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java55
-rw-r--r--libs/androidfw/Android.bp8
-rw-r--r--libs/androidfw/AssetManager2.cpp86
-rw-r--r--libs/androidfw/StringPool.cpp28
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h4
-rw-r--r--libs/androidfw/include/androidfw/CombinedIterator.h176
-rw-r--r--libs/androidfw/include/androidfw/StringPool.h5
-rw-r--r--libs/androidfw/tests/AndroidTest_Benchmarks.xml32
-rw-r--r--libs/androidfw/tests/AssetManager2_bench.cpp20
-rw-r--r--libs/androidfw/tests/BenchmarkHelpers.cpp4
-rw-r--r--libs/androidfw/tests/CombinedIterator_test.cpp98
-rw-r--r--libs/androidfw/tests/Theme_bench.cpp30
-rw-r--r--libs/hwui/Android.bp10
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp10
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h13
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp7
-rw-r--r--libs/hwui/renderthread/VulkanManager.h7
350 files changed, 7637 insertions, 1981 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 290fefa5abfa..544f0f38f48c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -169,6 +169,11 @@ class DividerPresenter implements View.OnTouchListener {
@GuardedBy("mLock")
private int mDividerPosition;
+ /** Indicates if there are containers to be finished since the divider has appeared. */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ private boolean mHasContainersToFinish = false;
+
DividerPresenter(int taskId, @NonNull DragEventCallback dragEventCallback,
@NonNull Executor callbackExecutor) {
mTaskId = taskId;
@@ -180,7 +185,8 @@ class DividerPresenter implements View.OnTouchListener {
void updateDivider(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentParentInfo parentInfo,
- @Nullable SplitContainer topSplitContainer) {
+ @Nullable SplitContainer topSplitContainer,
+ boolean isTaskFragmentVanished) {
if (!Flags.activityEmbeddingInteractiveDividerFlag()) {
return;
}
@@ -188,6 +194,18 @@ class DividerPresenter implements View.OnTouchListener {
synchronized (mLock) {
// Clean up the decor surface if top SplitContainer is null.
if (topSplitContainer == null) {
+ // Check if there are containers to finish but the TaskFragment hasn't vanished yet.
+ // Don't remove the decor surface and divider if so as the removal should happen in
+ // a following step when the TaskFragment has vanished. This ensures that the decor
+ // surface is removed only after the resulting Activity is ready to be shown,
+ // otherwise there may be flicker.
+ if (mHasContainersToFinish) {
+ if (isTaskFragmentVanished) {
+ setHasContainersToFinish(false);
+ } else {
+ return;
+ }
+ }
removeDecorSurfaceAndDivider(wct);
return;
}
@@ -868,11 +886,15 @@ class DividerPresenter implements View.OnTouchListener {
}
}
+ void setHasContainersToFinish(boolean hasContainersToFinish) {
+ synchronized (mLock) {
+ mHasContainersToFinish = hasContainersToFinish;
+ }
+ }
+
private static boolean isDraggingToFullscreenAllowed(
@NonNull DividerAttributes dividerAttributes) {
- // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
- // updated to v7.
- return false;
+ return dividerAttributes.isDraggingToFullscreenAllowed();
}
/**
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 f9a6caf42e6e..9ea2943bc6da 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
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;
@@ -29,6 +30,7 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+import android.annotation.ColorInt;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -48,6 +50,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -391,13 +394,34 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
- final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ final TaskFragmentAnimationParams.Builder builder =
+ new TaskFragmentAnimationParams.Builder();
+ final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
+ builder.setAnimationBackgroundColor(animationBackgroundColor);
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ final int openAnimationResId =
+ splitAttributes.getAnimationParams().getOpenAnimationResId();
+ builder.setOpenAnimationResId(openAnimationResId);
+ final int closeAnimationResId =
+ splitAttributes.getAnimationParams().getCloseAnimationResId();
+ builder.setCloseAnimationResId(closeAnimationResId);
+ final int changeAnimationResId =
+ splitAttributes.getAnimationParams().getChangeAnimationResId();
+ builder.setChangeAnimationResId(changeAnimationResId);
+ }
+ return builder.build();
+ }
+
+ @ColorInt
+ private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
+ int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
+ AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ animationBackground = splitAttributes.getAnimationParams().getAnimationBackground();
+ }
if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
- return new TaskFragmentAnimationParams.Builder()
- .setAnimationBackgroundColor(colorBackground.getColor())
- .build();
- } else {
- return TaskFragmentAnimationParams.DEFAULT;
+ animationBackgroundColor = colorBackground.getColor();
}
+ return animationBackgroundColor;
}
}
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 f78e2b5170fc..7ddda1f98809 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -673,7 +673,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
break;
case TYPE_TASK_FRAGMENT_VANISHED:
mPresenter.removeTaskFragmentInfo(info);
- onTaskFragmentVanished(wct, info);
+ onTaskFragmentVanished(wct, info, taskId);
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
onTaskFragmentParentInfoChanged(wct, taskId,
@@ -834,7 +834,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentInfo taskFragmentInfo) {
+ @NonNull TaskFragmentInfo taskFragmentInfo, int taskId) {
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container != null) {
// Cleanup if the TaskFragment vanished is not requested by the organizer.
@@ -843,6 +843,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateContainersInTaskIfVisible(wct, container.getTaskId());
}
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
+ final TaskContainer taskContainer = getTaskContainer(taskId);
+ if (taskContainer != null) {
+ // Update the divider to clean up any decor surfaces.
+ updateDivider(wct, taskContainer, true /* isTaskFragmentVanished */);
+ }
}
/**
@@ -884,7 +889,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// The divider need to be updated even if shouldUpdateContainer is false, because the decor
// surface may change in TaskFragmentParentInfo, which requires divider update but not
// container update.
- updateDivider(wct, taskContainer);
+ updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */);
// If the last direct activity of the host task is dismissed and there's an always-on-top
// overlay container in the task, the overlay container should also be dismissed.
@@ -899,14 +904,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) {
final TaskContainer taskContainer = getTaskContainer(taskId);
- if (taskContainer != null && taskContainer.isVisible()) {
+ if (taskContainer == null) {
+ return;
+ }
+
+ if (taskContainer.isVisible()) {
updateContainersInTask(wct, taskContainer);
+ } else if (Flags.fixNoContainerUpdateWithoutResize()) {
+ // the TaskFragmentContainers need to be updated when the task becomes visible
+ taskContainer.mTaskFragmentContainersNeedsUpdate = true;
}
}
@GuardedBy("mLock")
private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
@NonNull TaskContainer taskContainer) {
+ taskContainer.mTaskFragmentContainersNeedsUpdate = false;
+
// Update all TaskFragments in the Task. Make a copy of the list since some may be
// removed on updating.
final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
@@ -3257,12 +3271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@GuardedBy("mLock")
- void updateDivider(
- @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
+ void updateDivider(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskContainer taskContainer, boolean isTaskFragmentVanished) {
final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
- dividerPresenter.updateDivider(
- wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
+ final SplitContainer topSplitContainer = taskContainer.getTopNonFinishingSplitContainer();
+ if (dividerPresenter != null) {
+ dividerPresenter.updateDivider(
+ wct, parentInfo, topSplitContainer, isTaskFragmentVanished);
+ }
}
@Override
@@ -3292,6 +3309,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
taskContainer.updateTopSplitContainerForDivider(
dividerPresenter, containersToFinish);
+ if (!containersToFinish.isEmpty()) {
+ dividerPresenter.setHasContainersToFinish(true);
+ }
for (final TaskFragmentContainer container : containersToFinish) {
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
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 d888fa9d6feb..eb1fc23d6b00 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -20,8 +20,10 @@ import static android.content.pm.PackageManager.MATCH_ALL;
import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
+import static androidx.window.extensions.embedding.SplitController.TAG;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
+import android.annotation.AnimRes;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration;
@@ -31,9 +33,11 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.View;
@@ -56,6 +60,7 @@ import androidx.window.extensions.layout.FoldingFeature;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
@@ -125,6 +130,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2;
/**
+ * The key of {@link ActivityStack} alignment relative to its parent container.
+ * <p>
+ * See {@link ContainerPosition} for possible values.
+ * <p>
+ * Note that this constants must align with the definition in WM Jetpack library.
+ */
+ private static final String KEY_ACTIVITY_STACK_ALIGNMENT =
+ "androidx.window.embedding.ActivityStackAlignment";
+
+ /**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}
*/
@@ -374,7 +389,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
- mController.updateDivider(wct, taskContainer);
+ mController.updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */);
}
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -649,14 +664,114 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// 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);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final int windowingMode = taskContainer.getWindowingModeForTaskFragment(relativeBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
- // Always use default animation for standalone ActivityStack.
- updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ if (container.isOverlay() && isOverlayTransitionSupported()) {
+ // Use the overlay transition for the overlay container if it's supported.
+ final TaskFragmentAnimationParams params = createOverlayAnimationParams(relativeBounds,
+ taskContainer.getBounds(), container);
+ updateAnimationParams(wct, fragmentToken, params);
+ } else {
+ // Otherwise, fallabck to use the default animation params.
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ }
setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
}
+ private static boolean isOverlayTransitionSupported() {
+ return Flags.moveAnimationOptionsToChange()
+ && Flags.activityEmbeddingOverlayPresentationFlag();
+ }
+
+ @NonNull
+ private static TaskFragmentAnimationParams createOverlayAnimationParams(
+ @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds,
+ @NonNull TaskFragmentContainer container) {
+ if (relativeBounds.isEmpty()) {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
+
+ final int positionFromOptions = container.getLaunchOptions()
+ .getInt(KEY_ACTIVITY_STACK_ALIGNMENT , -1);
+ final int position = positionFromOptions != -1 ? positionFromOptions
+ // Fallback to calculate from bounds if the info can't be retrieved from options.
+ : getOverlayPosition(relativeBounds, parentContainerBounds);
+
+ return new TaskFragmentAnimationParams.Builder()
+ .setOpenAnimationResId(getOpenAnimationResourcesId(position))
+ .setChangeAnimationResId(R.anim.overlay_task_fragment_change)
+ .setCloseAnimationResId(getCloseAnimationResourcesId(position))
+ .build();
+ }
+
+ @VisibleForTesting
+ @ContainerPosition
+ static int getOverlayPosition(
+ @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds) {
+ final Rect relativeParentBounds = new Rect(parentContainerBounds);
+ relativeParentBounds.offsetTo(0, 0);
+ final int leftMatch = (relativeParentBounds.left == relativeBounds.left) ? 1 : 0;
+ final int topMatch = (relativeParentBounds.top == relativeBounds.top) ? 1 : 0;
+ final int rightMatch = (relativeParentBounds.right == relativeBounds.right) ? 1 : 0;
+ final int bottomMatch = (relativeParentBounds.bottom == relativeBounds.bottom) ? 1 : 0;
+
+ // Flag format: {left|top|right|bottom}. Note that overlay container could be shrunk and
+ // centered, which makes only one of overlay container edge matches the parent container.
+ final int directionFlag = (leftMatch << 3) + (topMatch << 2) + (rightMatch << 1)
+ + bottomMatch;
+
+ final int position = switch (directionFlag) {
+ // Only the left edge match or only the right edge not match: should be on the left of
+ // the parent container.
+ case 0b1000, 0b1101 -> CONTAINER_POSITION_LEFT;
+ // Only the top edge match or only the bottom edge not match: should be on the top of
+ // the parent container.
+ case 0b0100, 0b1110 -> CONTAINER_POSITION_TOP;
+ // Only the right edge match or only the left edge not match: should be on the right of
+ // the parent container.
+ case 0b0010, 0b0111 -> CONTAINER_POSITION_RIGHT;
+ // Only the bottom edge match or only the top edge not match: should be on the bottom of
+ // the parent container.
+ case 0b0001, 0b1011 -> CONTAINER_POSITION_BOTTOM;
+ default -> {
+ Log.w(TAG, "Unsupported position:" + Integer.toBinaryString(directionFlag)
+ + " fallback to treat it as right. Relative parent bounds: "
+ + relativeParentBounds + ", relative overlay bounds:" + relativeBounds);
+ yield CONTAINER_POSITION_RIGHT;
+ }
+ };
+ return position;
+ }
+
+ @AnimRes
+ private static int getOpenAnimationResourcesId(@ContainerPosition int position) {
+ return switch (position) {
+ case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_open_from_left;
+ case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_open_from_top;
+ case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_open_from_right;
+ case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_open_from_bottom;
+ default -> {
+ Log.w(TAG, "Unknown position:" + position);
+ yield Resources.ID_NULL;
+ }
+ };
+ }
+
+ @AnimRes
+ private static int getCloseAnimationResourcesId(@ContainerPosition int position) {
+ return switch (position) {
+ case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_close_to_left;
+ case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_close_to_top;
+ case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_close_to_right;
+ case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_close_to_bottom;
+ default -> {
+ Log.w(TAG, "Unknown position:" + position);
+ yield Resources.ID_NULL;
+ }
+ };
+ }
+
/**
* Returns the expanded bounds if the {@code relBounds} violate minimum dimension or are not
* fully covered by the task bounds. Otherwise, returns {@code relBounds}.
@@ -757,7 +872,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
super.expandTaskFragment(wct, container);
- mController.updateDivider(wct, container.getTaskContainer());
+ mController.updateDivider(
+ wct, container.getTaskContainer(), false /* isTaskFragmentVanished */);
}
static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
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 ee00c4cd67eb..20ad53ee19a8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -108,6 +108,12 @@ class TaskContainer {
private boolean mPlaceholderRuleSuppressed;
/**
+ * {@code true} if the TaskFragments in this Task needs to be updated next time the Task
+ * becomes visible. See {@link #shouldUpdateContainer(TaskFragmentParentInfo)}
+ */
+ boolean mTaskFragmentContainersNeedsUpdate;
+
+ /**
* The {@link TaskContainer} constructor
*
* @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
@@ -185,7 +191,8 @@ class TaskContainer {
// If the task properties equals regardless of starting position, don't
// need to update the container.
- return mInfo.getConfiguration().diffPublicOnly(configuration) != 0
+ return mTaskFragmentContainersNeedsUpdate
+ || mInfo.getConfiguration().diffPublicOnly(configuration) != 0
|| mInfo.getDisplayId() != info.getDisplayId();
}
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 070fa5bcfae4..859bc2cc40f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
@@ -41,6 +42,7 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
+import androidx.annotation.VisibleForTesting;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
@@ -138,6 +140,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
+ " an Activity, WindowContext or InputMethodService");
}
+ if (context.getAssociatedDisplayId() == INVALID_DISPLAY) {
+ Log.w(TAG, "The registered Context is a UI Context but not associated with any"
+ + " display. This Context may not receive any WindowLayoutInfo update");
+ }
mFoldingFeatureProducer.getData((features) -> {
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
@@ -257,7 +263,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+ @VisibleForTesting
+ void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
synchronized (mLock) {
mLastReportedFoldingFeatures.clear();
mLastReportedFoldingFeatures.addAll(storedFeatures);
@@ -409,9 +416,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* @return true if the display features should be reported for the UI Context, false otherwise.
*/
private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
- int displayId = context.getDisplay().getDisplayId();
+ int displayId = context.getAssociatedDisplayId();
if (displayId != DEFAULT_DISPLAY) {
- // Display features are not supported on secondary displays.
+ // Display features are not supported on secondary displays or the context is not
+ // associated with any display.
return false;
}
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 4267749dfa6b..c5aaddc4e0ed 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -29,6 +29,7 @@ import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.extensions.embedding.AnimationBackground;
+import androidx.window.extensions.embedding.AnimationParams;
import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
@@ -112,5 +113,13 @@ public class WindowExtensionsTest {
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
assertThat(splitAttributes.getAnimationBackground())
.isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+ assertThat(splitAttributes.getAnimationParams().getAnimationBackground())
+ .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+ assertThat(splitAttributes.getAnimationParams().getOpenAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+ assertThat(splitAttributes.getAnimationParams().getCloseAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+ assertThat(splitAttributes.getAnimationParams().getChangeAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 4f51815ed05d..bc18cd289e05 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -82,6 +82,7 @@ import java.util.concurrent.Executor;
*/
@Presubmit
@SmallTest
+@SuppressWarnings("GuardedBy")
@RunWith(AndroidJUnit4.class)
public class DividerPresenterTest {
@Rule
@@ -186,7 +187,8 @@ public class DividerPresenterTest {
mDividerPresenter.updateDivider(
mTransaction,
mParentInfo,
- mSplitContainer);
+ mSplitContainer,
+ false /* isTaskFragmentVanished */);
assertNotEquals(mProperties, mDividerPresenter.mProperties);
verify(mRenderer).update();
@@ -206,7 +208,8 @@ public class DividerPresenterTest {
mDividerPresenter.updateDivider(
mTransaction,
mParentInfo,
- mSplitContainer);
+ mSplitContainer,
+ false /* isTaskFragmentVanished */);
assertNotEquals(mProperties, mDividerPresenter.mProperties);
verify(mRenderer).update();
@@ -222,7 +225,8 @@ public class DividerPresenterTest {
mDividerPresenter.updateDivider(
mTransaction,
mParentInfo,
- mSplitContainer);
+ mSplitContainer,
+ false /* isTaskFragmentVanished */);
assertEquals(mProperties, mDividerPresenter.mProperties);
verify(mRenderer, never()).update();
@@ -234,7 +238,42 @@ public class DividerPresenterTest {
mDividerPresenter.updateDivider(
mTransaction,
mParentInfo,
- null /* splitContainer */);
+ null /* splitContainer */,
+ false /* isTaskFragmentVanished */);
+ final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
+ .build();
+
+ verify(mTransaction).addTaskFragmentOperation(
+ mPrimaryContainerToken, taskFragmentOperation);
+ verify(mRenderer).release();
+ assertNull(mDividerPresenter.mRenderer);
+ assertNull(mDividerPresenter.mProperties);
+ assertNull(mDividerPresenter.mDecorSurfaceOwner);
+ }
+
+ @Test
+ public void testUpdateDivider_noChangeWhenHasContainersToFinishButTaskFragmentNotVanished() {
+ mDividerPresenter.setHasContainersToFinish(true);
+ mDividerPresenter.updateDivider(
+ mTransaction,
+ mParentInfo,
+ null /* splitContainer */,
+ false /* isTaskFragmentVanished */);
+
+ assertEquals(mProperties, mDividerPresenter.mProperties);
+ verify(mRenderer, never()).update();
+ verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
+ }
+
+ @Test
+ public void testUpdateDivider_dividerRemovedWhenHasContainersToFinishAndTaskFragmentVanished() {
+ mDividerPresenter.setHasContainersToFinish(true);
+ mDividerPresenter.updateDivider(
+ mTransaction,
+ mParentInfo,
+ null /* splitContainer */,
+ true /* isTaskFragmentVanished */);
final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder(
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
.build();
@@ -254,7 +293,8 @@ public class DividerPresenterTest {
mDividerPresenter.updateDivider(
mTransaction,
mParentInfo,
- mSplitContainer);
+ mSplitContainer,
+ false /* isTaskFragmentVanished */);
final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder(
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
.build();
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 7a0b9a0ece6b..325750243744 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
@@ -30,6 +30,11 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
+import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition;
import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -666,8 +671,8 @@ public class OverlayPresentationTest {
attributes.getRelativeBounds());
verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
WINDOWING_MODE_MULTI_WINDOW);
- verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
- TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token),
+ any(TaskFragmentAnimationParams.class));
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
any(TaskFragmentContainer.class), anyBoolean());
@@ -691,8 +696,8 @@ public class OverlayPresentationTest {
attributes.getRelativeBounds());
verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction,
container, WINDOWING_MODE_MULTI_WINDOW);
- verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
- TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token),
+ any(TaskFragmentAnimationParams.class));
verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
any(TaskFragmentContainer.class), anyBoolean());
verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true);
@@ -870,6 +875,59 @@ public class OverlayPresentationTest {
eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
}
+ // TODO(b/243518738): Rewrite with TestParameter.
+ @Test
+ public void testGetOverlayPosition() {
+ assertWithMessage("It must be position left for left overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right / 2,
+ TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
+ assertWithMessage("It must be position left for shrunk left overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top + 20,
+ TASK_BOUNDS.right / 2,
+ TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
+ assertWithMessage("It must be position left for top overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
+ assertWithMessage("It must be position left for shrunk top overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left + 20,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right - 20,
+ TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
+ assertWithMessage("It must be position left for right overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.right / 2,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
+ assertWithMessage("It must be position left for shrunk right overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.right / 2,
+ TASK_BOUNDS.top + 20,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
+ assertWithMessage("It must be position left for bottom overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.bottom / 2,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+ assertWithMessage("It must be position left for shrunk bottom overlay.")
+ .that(getOverlayPosition(new Rect(
+ TASK_BOUNDS.left + 20,
+ TASK_BOUNDS.bottom / 20,
+ TASK_BOUNDS.right - 20,
+ TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+ }
+
/**
* A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
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 640b1fced455..efeec82b782e 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
@@ -200,12 +200,14 @@ public class SplitControllerTest {
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
+ doReturn(createTestTaskContainer()).when(mSplitController).getTaskContainer(TASK_ID);
// The TaskFragment has been removed in the server, we only need to cleanup the reference.
- mSplitController.onTaskFragmentVanished(mTransaction, mInfo);
+ mSplitController.onTaskFragmentVanished(mTransaction, mInfo, TASK_ID);
verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
verify(mSplitController).removeContainer(tf);
+ verify(mSplitController).updateDivider(any(), any(), anyBoolean());
verify(mTransaction, never()).finishActivity(any());
}
@@ -1152,7 +1154,7 @@ public class SplitControllerTest {
.setTaskFragmentInfo(info));
mSplitController.onTransactionReady(transaction);
- verify(mSplitController).onTaskFragmentVanished(any(), eq(info));
+ verify(mSplitController).onTaskFragmentVanished(any(), eq(info), anyInt());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
new file mode 100644
index 000000000000..ff0a82fe05d6
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * Test class for {@link WindowLayoutComponentImpl}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:WindowLayoutComponentImplTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WindowLayoutComponentImplTest {
+
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+ @Before
+ public void setUp() {
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(
+ ApplicationProvider.getApplicationContext(),
+ mock(DeviceStateManagerFoldingFeatureProducer.class));
+ }
+
+ @Test
+ public void testAddWindowLayoutListenerOnFakeUiContext_noCrash() {
+ final Context fakeUiContext = createTestContext();
+
+ mWindowLayoutComponent.addWindowLayoutInfoListener(fakeUiContext, info -> {});
+
+ mWindowLayoutComponent.onDisplayFeaturesChanged(Collections.emptyList());
+ }
+
+ private static Context createTestContext() {
+ return new FakeUiContext(ApplicationProvider.getApplicationContext());
+ }
+
+ /**
+ * A {@link android.content.Context} overrides {@link android.content.Context#isUiContext} to
+ * {@code true}.
+ */
+ private static class FakeUiContext extends ContextWrapper {
+
+ FakeUiContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public boolean isUiContext() {
+ return true;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 25d3067a34bc..dbcad8aab45b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -88,7 +88,7 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-class com.android.internal.protolog.ProtoLog " +
"--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 " +
@@ -107,7 +107,7 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
"--viewer-config-type json " +
@@ -124,7 +124,7 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
"--viewer-config-type proto " +
@@ -190,6 +190,15 @@ java_library {
],
}
+java_library {
+ name: "WindowManager-Shell-shared-desktopMode",
+
+ srcs: [
+ "shared/**/desktopmode/*.java",
+ "shared/**/desktopmode/*.kt",
+ ],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index bcb1d292fce2..52ae93f5ebf1 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -29,6 +29,7 @@
android:name=".desktopmode.DesktopWallpaperActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
+ android:showForAllUsers="true"
android:theme="@style/DesktopWallpaperTheme" />
<activity
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 112eb617e7a6..3ff40e0886a4 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -121,3 +121,20 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_taskbar_on_phones"
+ namespace: "multitasking"
+ description: "Enables taskbar on phones"
+ bug: "348007377"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_bubble_bar_in_persistent_task_bar"
+ namespace: "multitasking"
+ description: "Enable bubble bar to be shown in the persistent task bar"
+ bug: "346391377"
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
new file mode 100644
index 000000000000..1871203c7600
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -0,0 +1,95 @@
+// 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 {
+ // 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: "WMShellRobolectricScreenshotTestApp",
+ platform_apis: true,
+ certificate: "platform",
+ static_libs: [
+ "WindowManager-Shell",
+ "platform-screenshot-diff-core",
+ ],
+ asset_dirs: ["goldens/robolectric"],
+ manifest: "AndroidManifestRobolectric.xml",
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "WMShellRobolectricScreenshotTests",
+ instrumentation_for: "WMShellRobolectricScreenshotTestApp",
+ upstream: true,
+ java_resource_dirs: [
+ "robolectric/config",
+ ],
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "truth",
+ "platform-parametric-runner-lib",
+ ],
+ auto_gen_config: true,
+}
+
+android_test {
+ name: "WMShellMultivalentScreenshotTestsOnDevice",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "WindowManager-Shell",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "truth",
+ "platform-parametric-runner-lib",
+ "platform-screenshot-diff-core",
+ ],
+ 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",
+ asset_dirs: ["goldens/onDevice"],
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
new file mode 100644
index 000000000000..467dc6a5cb81
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalentscreenshot">
+
+ <application android:debuggable="true" android:supportsRtl="true" >
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Multivalent screenshot tests for WindowManager-Shell"
+ android:targetPackage="com.android.wm.shell.multivalentscreenshot">
+ </instrumentation>
+
+ <!-- this permission is required by Tuner Service in screenshot tests -->
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
new file mode 100644
index 000000000000..b4bdaeaf0eac
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalentscreenshot">
+ <application android:debuggable="true" android:supportsRtl="true">
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
new file mode 100644
index 000000000000..75793ae69d27
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <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="WMShellMultivalentScreenshotTestsOnDevice.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="WMShellMultivalentScreenshotTestsOnDevice" />
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.wm.shell.multivalentscreenshot/files/wmshell_screenshots" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.wm.shell.multivalentscreenshot" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS
new file mode 100644
index 000000000000..dc11241fb76b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+mpodolian@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
new file mode 100644
index 000000000000..027b28e7ace7
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
new file mode 100644
index 000000000000..027b28e7ace7
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
new file mode 100644
index 000000000000..e02c89ae07bd
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
new file mode 100644
index 000000000000..e02c89ae07bd
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
new file mode 100644
index 000000000000..d50d976c9e84
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
@@ -0,0 +1,3 @@
+sdk=NEWEST_SDK
+graphicsMode=NATIVE
+
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
new file mode 100644
index 000000000000..d35f493a8f60
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.view.LayoutInflater
+import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager
+import com.android.wm.shell.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+@RunWith(ParameterizedAndroidJunit4::class)
+class BubbleEducationViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.forDisplays(Displays.Phone, isLandscape = false)
+ }
+
+ @get:Rule
+ val screenshotRule =
+ ViewScreenshotTestRule(
+ emulationSpec,
+ WMShellGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
+ )
+
+ @Test
+ fun bubblesEducation() {
+ screenshotRule.screenshotTest("bubbles_education") { activity ->
+ activity.actionBar?.hide()
+ val view =
+ LayoutInflater.from(activity)
+ .inflate(R.layout.bubble_bar_stack_education, null) as BubblePopupView
+ view.setup()
+ view
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
new file mode 100644
index 000000000000..901b79b9b1a0
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.testing.goldenpathmanager
+
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+
+/** A WM Shell specific implementation of [GoldenPathManager]. */
+class WMShellGoldenPathManager(pathConfig: PathConfig) :
+ GoldenPathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetPath,
+ deviceLocalPath = deviceLocalPath,
+ pathConfig = pathConfig,
+ ) {
+
+ private companion object {
+ private const val ASSETS_PATH =
+ "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice"
+ private const val ASSETS_PATH_ROBO =
+ "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/" +
+ "robolectric"
+ private val assetPath: String
+ get() = if (Build.FINGERPRINT.contains("robolectric")) ASSETS_PATH_ROBO else ASSETS_PATH
+ private val deviceLocalPath: String
+ get() =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/wmshell_screenshots"
+ }
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "WMShellGoldenPathManager"
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
new file mode 120000
index 000000000000..e879efc81ec1
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
@@ -0,0 +1 @@
+multivalentScreenshotTests \ No newline at end of file
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
index 9e1440d5716b..ae60d8bc2596 100644
--- 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
@@ -27,7 +27,7 @@ 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.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
import com.android.wm.shell.common.bubbles.BubbleBarLocation
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
index 327e2059557c..5e673338bad3 100644
--- 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
@@ -32,7 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index ace2c131050c..935d12916f56 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -27,7 +27,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.DeviceConfig
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
new file mode 100644
index 000000000000..7d912a24c443
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/>
+</vector>
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 34f03c2f226b..501bedd50f55 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
- android:id="@+id/bubble_bar_expanded_view">
+ android:id="@+id/bubble_expanded_view">
<com.android.wm.shell.bubbles.bar.BubbleBarHandleView
android:id="@+id/bubble_bar_handle_view"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index d5724cc6a420..419d5c0af1a4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -135,5 +135,24 @@
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/open_in_browser_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="vertical"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/open_in_browser_button"
+ android:contentDescription="@string/open_in_browser_text"
+ android:text="@string/open_in_browser_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+ </LinearLayout>
</LinearLayout>
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 7d5f9cdbebc8..5fe3f2af63a0 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
@@ -14,88 +14,100 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/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"
- android:orientation="horizontal"
- android:gravity="center"
- android:padding="16dp"
android:background="@drawable/desktop_mode_maximize_menu_background"
android:elevation="1dp">
<LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:id="@+id/container"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:orientation="horizontal"
+ android:padding="16dp"
+ android:gravity="center">
- <Button
- android:layout_width="94dp"
- android:layout_height="60dp"
- android:id="@+id/maximize_menu_maximize_button"
- style="?android:attr/buttonBarButtonStyle"
- android:stateListAnimator="@null"
- android:layout_marginRight="8dp"
- android:layout_marginBottom="4dp"
- android:alpha="0"/>
-
- <TextView
- android:id="@+id/maximize_menu_maximize_window_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
- android:textSize="11sp"
- android:layout_marginBottom="76dp"
- android:gravity="center"
- android:fontFamily="google-sans-text"
- android:text="@string/desktop_mode_maximize_menu_maximize_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"
- android:alpha="0"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
<LinearLayout
- android:id="@+id/maximize_menu_snap_menu_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:padding="4dp"
- android:background="@drawable/desktop_mode_maximize_menu_layout_background"
- android:layout_marginBottom="4dp"
- android:alpha="0">
- <Button
- android:id="@+id/maximize_menu_snap_left_button"
- style="?android:attr/buttonBarButtonStyle"
- android:layout_width="41dp"
- android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
- android:layout_marginRight="4dp"
- android:background="@drawable/desktop_mode_maximize_menu_button_background"
- android:stateListAnimator="@null"/>
+ android:orientation="vertical">
<Button
- android:id="@+id/maximize_menu_snap_right_button"
+ android:layout_width="94dp"
+ android:layout_height="60dp"
+ android:id="@+id/maximize_menu_maximize_button"
style="?android:attr/buttonBarButtonStyle"
- android:layout_width="41dp"
- android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
- android:background="@drawable/desktop_mode_maximize_menu_button_background"
- android:stateListAnimator="@null"/>
+ android:stateListAnimator="@null"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="4dp"
+ android:alpha="0"/>
+
+ <TextView
+ android:id="@+id/maximize_menu_maximize_window_text"
+ android:layout_width="94dp"
+ android:layout_height="18dp"
+ android:textSize="11sp"
+ android:layout_marginBottom="76dp"
+ android:gravity="center"
+ android:fontFamily="google-sans-text"
+ android:text="@string/desktop_mode_maximize_menu_maximize_text"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/maximize_menu_snap_menu_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="4dp"
+ android:background="@drawable/desktop_mode_maximize_menu_layout_background"
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
+ <Button
+ android:id="@+id/maximize_menu_snap_left_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="41dp"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
+ android:layout_marginRight="4dp"
+ android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_right_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="41dp"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
+ android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:stateListAnimator="@null"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/maximize_menu_snap_window_text"
+ android:layout_width="94dp"
+ android:layout_height="18dp"
+ android:textSize="11sp"
+ android:layout_marginBottom="76dp"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:fontFamily="google-sans-text"
+ android:text="@string/desktop_mode_maximize_menu_snap_text"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
- <TextView
- android:id="@+id/maximize_menu_snap_window_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
- android:textSize="11sp"
- android:layout_marginBottom="76dp"
- android:layout_gravity="center"
- android:gravity="center"
- android:fontFamily="google-sans-text"
- android:text="@string/desktop_mode_maximize_menu_snap_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"
- android:alpha="0"/>
</LinearLayout>
-</LinearLayout>
+
+ <!-- Empty view intentionally placed in front of everything else and matching the menu size
+ used to monitor input events over the entire menu. -->
+ <View
+ android:id="@+id/maximize_menu_overlay"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"/>
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 1c8f5e60c5c9..8b328e2c79cf 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Borrel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tik om hierdie app te herbegin vir ’n beter aansig"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Verander hierdie app se aspekverhouding in Instellings"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Verander aspekverhouding"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 81ab3ab15aad..b005a01711eb 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 3974c39d4803..8c283d33eff6 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index a1ce1b3b9513..ef92587ad274 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 71dfe5ac6bed..04b2f1c23bc4 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Qabarcıq"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Yaxşı görünüş üçün toxunaraq bu tətbiqi yenidən başladın"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ayarlarda bu tətbiqin tərəflər nisbətini dəyişin"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tərəflər nisbətini dəyişin"</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 f48360991d49..47bc105f19bc 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promenite razmeru ove aplikacije u Podešavanjima"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promeni razmeru"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 532ecc64358f..6ad7553a8383 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 8f828badcf47..a9e0bce81376 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index e0a2ea824be0..29de1007e311 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 41c72c1d3a03..5f1da7571d95 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijenite format slike aplikacije u Postavkama"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijenite format slike"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 679227248ea5..d70de794ba17 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bombolla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Canvia la relació d\'aspecte d\'aquesta aplicació a Configuració"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Canvia la relació d\'aspecte"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 150a6e642529..ca00fec12e86 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Klepnutím tuto aplikaci restartujete kvůli lepšímu zobrazení"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Změnit v Nastavení poměr stran této aplikace"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Změnit poměr stran"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 8878910a4d2c..d50d2f0f135d 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tryk for at genstarte denne app, så visningen forbedres"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Skift denne apps billedformat i Indstillinger"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Skift billedformat"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 7b91559e9179..7f44f83a1790 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tippen, um diese App neu zu starten und die Ansicht zu verbessern"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Seitenverhältnis der App in den Einstellungen ändern"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Seitenverhältnis ändern"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 14e5e2f87ab8..a3a5ccd839e4 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 7427b62679be..edc4f4e25c2a 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index cb9ee4f6b6b3..d7e23fd8dfd8 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -84,6 +84,8 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
+ <string name="bubble_shortcut_label" msgid="666269077944378311">"Bubbles"</string>
+ <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Show Bubbles"</string>
<string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 7427b62679be..edc4f4e25c2a 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 7427b62679be..edc4f4e25c2a 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 8498807f9fdb..1da8c275ce54 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -84,6 +84,8 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎‎Bubble‎‏‎‎‏‎"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎Manage‎‏‎‎‏‎"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎Bubble dismissed.‎‏‎‎‏‎"</string>
+ <string name="bubble_shortcut_label" msgid="666269077944378311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎Bubbles‎‏‎‎‏‎"</string>
+ <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‎Show Bubbles‎‏‎‎‏‎"</string>
<string name="restart_button_description" msgid="4564728020654658478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎Tap to restart this app for a better view‎‏‎‎‏‎"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‎Change this app\'s aspect ratio in Settings‎‏‎‎‏‎"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎Change aspect ratio‎‏‎‎‏‎"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 406c1f37c455..8653e5932a04 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Cuadro"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Presiona para reiniciar esta app y tener una mejor vista"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta app en Configuración"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 0583d79da127..8f59c9c91d20 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toca para reiniciar esta aplicación y obtener una mejor vista"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta aplicación en Ajustes"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 70547f566ea6..3d86eb4a91d9 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Mull"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muutke selle rakenduse kuvasuhet jaotises Seaded"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Muutke kuvasuhet"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 4be35eac6c1f..4e7bdd246d10 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuila"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Hobeto ikusteko, sakatu hau, eta aplikazioa berrabiarazi egingo da"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Aldatu aplikazioaren aspektu-erlazioa ezarpenetan"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Aldatu aspektu-erlazioa"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 32d5f5f34fb8..a4711d4180bc 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -53,7 +53,7 @@
<string name="accessibility_split_top" msgid="2789329702027147146">"تقسیم از بالا"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسیم از پایین"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یک‌دستی"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحه‌نمایش تند به‌طرف بالا بکشید یا در هر جایی از بالای برنامه که می‌خواهید ضربه بزنید"</string>
+ <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>
@@ -71,28 +71,32 @@
<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_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_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_bar_education_stack_text" msgid="2446934610817409820">"مکالمه‌های جدید به‌صورت نماد در گوشه پایین صفحه‌نمایش نشان داده می‌شود. برای ازهم بازکردن آن‌ها ضربه بزنید یا برای بستن، آن‌ها را بکشید."</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_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="restart_button_description" msgid="4564728020654658478">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراه‌اندازی شود"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
+ <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>
- <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string>
- <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string>
- <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه تک‌ضرب بزنید"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن تک‌ضرب بزنید"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن تک‌ضرب بزنید."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دو تک‌ضرب بزنید"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراه‌اندازی شود؟"</string>
@@ -100,7 +104,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراه‌اندازی"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
- <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدوضربه بزنید"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدو تک‌ضرب بزنید"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
<string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 6f03545e5542..577d625ba8f8 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Kupla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muuta tämän sovelluksen kuvasuhdetta Asetuksissa"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Vaihda kuvasuhdetta"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 3492f136c4f9..7feb772eb10a 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -31,11 +31,11 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ajouter à la réserve"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge l\'écran partagé"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette application ne peut être ouverte que dans une seule fenêtre."</string>
- <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
- <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner avec l\'écran partagé"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'appli ne prend pas en charge l\'écran partagé"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette appli ne peut être ouverte que dans une seule fenêtre."</string>
+ <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'appli ne fonctionne pas sur un écran secondaire."</string>
+ <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'appli ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
<string name="divider_title" msgid="1963391955593749442">"Séparateur d\'écran partagé"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string>
@@ -56,7 +56,7 @@
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'appli"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode Une main"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'appli <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu déroulant"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Replacer sur la pile"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
@@ -73,7 +73,7 @@
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette application"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette appli"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et les bulles ignorées s\'afficheront ici"</string>
@@ -84,29 +84,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string>
- <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage"</string>
- <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramètres"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
+ <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette appli afin d\'obtenir un meilleur affichage"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette appli dans les paramètres"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
- <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre application pour utiliser l\'écran partagé"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour un meilleur affichage?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
- <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette application"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette appli"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
- <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'appli"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
<string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
<string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 4002e4d04d51..4d14d0b85f3e 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Appuyez pour redémarrer cette appli et obtenir une meilleure vue."</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Modifiez le format de cette appli dans les Paramètres."</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier le format"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index c371f7f62feb..e5b67c2aaad1 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia a proporción desta aplicación en Configuración"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar a proporción"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 7e3d7a373be4..e2a52dccd8ea 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index cd0f4e3618f7..f75e0e0528e1 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 27d4cfcf22d5..ed80c505d756 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijeni omjer slike ove aplikacije u postavkama"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijeni omjer slike"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index a8cc5c120efc..32a31063bd90 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Buborék"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Az app méretarányát a Beállításokban módosíthatja"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Méretarány módosítása"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 7f372774241a..65ca704ded09 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 3cf55fa0ede2..975dd72f67d7 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ubah rasio aspek aplikasi ini di Setelan"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ubah rasio aspek"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 6aa56f9858ad..11c47189dce0 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Ýttu til að endurræsa forritið og fá betri sýn"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Breyta myndhlutfalli þessa forrits í stillingunum"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Breyta myndhlutfalli"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 3c1d5e4dac02..168c8cc5936f 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tocca per riavviare l\'app e migliorare la visualizzazione"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia le proporzioni dell\'app nelle Impostazioni"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambia proporzioni"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index a0c3b3a95ca8..fd4cd1adaa2a 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index fb726c180997..64ddec9450ae 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index e9f620a17203..cab8807b86d1 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 34e41038f285..4ff5b85b36fd 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 362bbad4ec12..ba7a32495659 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោល​សារលេចឡើង។"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 77cc4a44f81a..423e8d53a654 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index caa114fd6f88..0d1c6216776b 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 302c0071a73a..f17e9ca891c0 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index a3519636b71f..195e4d56a1c1 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ຟອງ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index e4dd7398f679..63ad580a81cc 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Debesėlis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Pakeiskite šios programos kraštinių santykį"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Keisti kraštinių santykį"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 99aebf626322..268d89324f54 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Iestatījumos mainiet šīs lietotnes malu attiecību."</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mainīt malu attiecību"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index c152c60fa631..0a0027fa1bae 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 90275cdb517a..07809e1a0014 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 5e43506ab621..99bd2dffca53 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 5874bffc9199..ac57e0a549b4 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 4de8a7b03547..6bc2fbb27c51 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Gelembung"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Tukar nisbah bidang apl ini dalam Tetapan"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tukar nisbah bidang"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 5b9e9cb7353e..12c19edcaeb1 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 6005be4fb5c9..27b79019a57a 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Trykk for å starte denne appen på nytt og få en bedre visning"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Endre høyde/bredde-forholdet for denne appen i Innstillinger"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre høyde/bredde-forholdet"</string>
@@ -113,7 +117,7 @@
<string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
<string name="select_text" msgid="5139083974039906583">"Velg"</string>
- <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skjermbilde"</string>
<string name="close_text" msgid="4986518933445178928">"Lukk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
<string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index a5bd2ab5c10b..25d033738c86 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 0cd27c5c1457..4ad343cb1a4e 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tik om deze app opnieuw op te starten voor een betere weergave"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Wijzig de beeldverhouding van deze app in Instellingen"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Beeldverhouding wijzigen"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index bf751852a255..966d40440ac7 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 325c1e80c433..9feaf41cda7f 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index a7648c8e323b..1c7fbf8e80d3 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Dymek"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Kliknij w celu zrestartowania aplikacji, aby lepiej się wyświetlała."</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmień proporcje obrazu aplikacji w Ustawieniach"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmień proporcje obrazu"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index e47d151337b2..5c2de2ad0d31 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 1210fe8fda05..6f76525473eb 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar esta app e ficar com uma melhor visão"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Altere o formato desta app nas Definições"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Altere o formato"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index e47d151337b2..5c2de2ad0d31 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index ae871f3dd42b..6e85e7849d95 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Atinge ca să repornești aplicația pentru o vizualizare mai bună"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Schimbă raportul de dimensiuni al aplicației din Setări"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Schimbă raportul de dimensiuni"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 971e146ba77e..1b41983cd1a3 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index ef1381cbe635..6fd37e91c8b0 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 55a03122483b..dabbf397d38f 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmeniť pomer strán tejto aplikácie v Nastaveniach"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmeniť pomer strán"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index bb123dcdbfb6..3ade33810cc8 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Mehurček"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Razmerje stranic te aplikacije spremenite v nastavitvah."</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Sprememba razmerja stranic"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index c74a8cd23338..ee1aa00c5cbe 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Flluskë"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Trokit për ta rinisur këtë aplikacion për një pamje më të mirë"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ndrysho raportin e pamjes së këtij aplikacioni te \"Cilësimet\""</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ndrysho raportin e pamjes"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 0694a973dc1e..b2868ca84dac 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 8e0bcfe91679..66118efd5da7 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Tryck för att starta om appen och få en bättre vy"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ändra appens bildformat i inställningarna"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ändra bildformat"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 41180abcf712..863b49b1f010 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Kiputo"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Badilisha uwiano wa programu hii katika Mipangilio"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Badilisha uwiano wa kipengele"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 01ac78d984f3..74e0207bf62e 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 6224e72c19fe..35711567e760 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index fe0b74c469f4..47694164270e 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 786e99cfe8c8..be18d88194c1 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"I-tap para i-restart ang app na ito para sa mas magandang view"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Baguhin ang aspect ratio ng app na ito sa Mga Setting"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Baguhin ang aspect ratio"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index e953f5808aff..4c8c53610711 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Baloncuk"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Bu uygulamanın en boy oranını Ayarlar\'dan değiştirin"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"En boy oranını değiştir"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index fbdf42e582d1..7cc1a0406f97 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 5562fa70bf09..8b9f29969d80 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 50e42329a1a0..55c6b32c909b 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Pufaklar"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Sozlamalar orqali bu ilovaning tomonlar nisbatini oʻzgartiring"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tomonlar nisbatini oʻzgartirish"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 6da85881210d..07a6b6f6c2b4 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Nhấn nút khởi động lại ứng dụng này để xem dễ hơn"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Thay đổi tỷ lệ khung hình của ứng dụng này thông qua phần Cài đặt"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Thay đổi tỷ lệ khung hì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 4318caf26199..908095a163a7 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭消息气泡。"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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 72cd39d8e00a..c8550b4e0611 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index c06d7b105694..67048335de64 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<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-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 755414e52762..96b4faec06b8 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -84,6 +84,10 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Ibhamuza"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string>
+ <!-- no translation found for bubble_shortcut_label (666269077944378311) -->
+ <skip />
+ <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
+ <skip />
<string name="restart_button_description" msgid="4564728020654658478">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Shintsha ukubukeka kwesilinganiselo kwe-app kuMasethingi"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Shintsha ukubukeka kwesilinganiselo"</string>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 595d34664cfa..d143263b69a5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -507,8 +507,11 @@
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+ <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+
<!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
+ <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 47846746b205..4e7cfb638a12 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -280,6 +280,8 @@
<string name="select_text">Select</string>
<!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] -->
<string name="screenshot_text">Screenshot</string>
+ <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
+ <string name="open_in_browser_text">Open in browser</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
new file mode 100644
index 000000000000..f0d80a02243a
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.shared.desktopmode
+
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.window.flags.Flags
+
+/*
+ * A shared class to check desktop mode flags state.
+ *
+ * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
+ * value and the developer option override state (if applicable).
+ **/
+enum class DesktopModeFlags(
+ // Function called to obtain aconfig flag value.
+ private val flagFunction: () -> Boolean,
+ // Whether the flag state should be affected by developer option.
+ private val shouldOverrideByDevOption: Boolean
+) {
+ // All desktop mode related flags will be added here
+ DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true);
+
+ /**
+ * Determines state of flag based on the actual flag and desktop mode developer option overrides.
+ *
+ * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
+ */
+ fun isEnabled(context: Context): Boolean =
+ if (!Flags.showDesktopWindowingDevOption() ||
+ !shouldOverrideByDevOption ||
+ context.contentResolver == null) {
+ flagFunction()
+ } else {
+ val shouldToggleBeEnabledByDefault = DesktopModeStatus.shouldDevOptionBeEnabledByDefault()
+ when (getToggleOverride(context)) {
+ ToggleOverride.OVERRIDE_UNSET -> flagFunction()
+ // When toggle override matches its default state, don't override flags. This helps users
+ // reset their feature overrides.
+ ToggleOverride.OVERRIDE_OFF ->
+ if (shouldToggleBeEnabledByDefault) false else flagFunction()
+ ToggleOverride.OVERRIDE_ON -> if (shouldToggleBeEnabledByDefault) flagFunction() else true
+ }
+ }
+
+ private fun getToggleOverride(context: Context): ToggleOverride {
+ val override =
+ cachedToggleOverride
+ ?: run {
+ val override = getToggleOverrideFromSystem(context)
+ // Cache toggle override the first time we encounter context. Override does not change
+ // with context, as context is just used to fetch System Property and Settings.Global
+ cachedToggleOverride = override
+ Log.d(TAG, "Toggle override initialized to: $override")
+ override
+ }
+
+ return override
+ }
+
+ private fun getToggleOverrideFromSystem(context: Context): ToggleOverride {
+ // A non-persistent System Property is used to store override to ensure it remains
+ // constant till reboot.
+ val overrideFromSystemProperties: ToggleOverride? =
+ System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride()
+ return overrideFromSystemProperties
+ ?: run {
+ // Read Setting Global if System Property is not present (just after reboot)
+ // or not valid (user manually changed the value)
+ val overrideFromSettingsGlobal =
+ convertToToggleOverrideWithFallback(
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ ToggleOverride.OVERRIDE_UNSET.setting),
+ ToggleOverride.OVERRIDE_UNSET)
+ // Initialize System Property
+ System.setProperty(
+ SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
+
+ overrideFromSettingsGlobal
+ }
+ }
+
+ /**
+ * Override state of desktop mode developer option toggle.
+ *
+ * @property setting The integer value that is associated with the developer option toggle
+ * override
+ */
+ enum class ToggleOverride(val setting: Int) {
+ /** No override is set. */
+ OVERRIDE_UNSET(-1),
+ /** Override to off. */
+ OVERRIDE_OFF(0),
+ /** Override to on. */
+ OVERRIDE_ON(1)
+ }
+
+ private fun String?.convertToToggleOverride(): ToggleOverride? {
+ val intValue = this?.toIntOrNull() ?: return null
+ return settingToToggleOverrideMap[intValue]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $intValue")
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeFlags"
+
+ /**
+ * Key for non-persistent System Property which is used to store desktop windowing developer
+ * option overrides.
+ */
+ private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+
+ /**
+ * Local cache for toggle override, which is initialized once on its first access. It needs to
+ * be refreshed only on reboots as overridden state takes effect on reboots.
+ */
+ private var cachedToggleOverride: ToggleOverride? = null
+
+ private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+
+ @JvmStatic
+ fun convertToToggleOverrideWithFallback(
+ overrideInt: Int,
+ fallbackOverride: ToggleOverride
+ ): ToggleOverride {
+ return settingToToggleOverrideMap[overrideInt]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $overrideInt")
+ fallbackOverride
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 4876f327a650..fc4710f8f67b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.shared;
+package com.android.wm.shell.shared.desktopmode;
import android.annotation.NonNull;
import android.content.Context;
@@ -27,8 +27,11 @@ import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
*/
+// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package
public class DesktopModeStatus {
+ private static final String TAG = "DesktopModeStatus";
+
/**
* Flag to indicate whether task resizing is veiled.
*/
@@ -98,15 +101,6 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
/**
- * Return {@code true} if desktop windowing is enabled. Only to be used for testing. Callers
- * should use {@link #canEnterDesktopMode(Context)} to query the state of desktop windowing.
- */
- @VisibleForTesting
- public static boolean isEnabled() {
- return Flags.enableDesktopWindowingMode();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -120,7 +114,7 @@ public class DesktopModeStatus {
*/
public static boolean useWindowShadow(boolean isFocusedWindow) {
return USE_WINDOW_SHADOWS
- || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
+ || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
}
/**
@@ -154,10 +148,24 @@ public class DesktopModeStatus {
}
/**
+ * Return {@code true} if desktop mode dev option should be shown on current device
+ */
+ public static boolean canShowDesktopModeDevOption(@NonNull Context context) {
+ return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption();
+ }
+
+ /** Returns if desktop mode dev option should be enabled if there is no user override. */
+ public static boolean shouldDevOptionBeEnabledByDefault() {
+ return Flags.enableDesktopWindowingMode();
+ }
+
+ /**
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
*/
public static boolean canEnterDesktopMode(@NonNull Context context) {
- return (!enforceDeviceRestrictions() || isDesktopModeSupported(context)) && isEnabled();
+ if (!isDeviceEligibleForDesktopMode(context)) return false;
+
+ return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
}
/**
@@ -181,4 +189,11 @@ public class DesktopModeStatus {
return DESKTOP_DENSITY_OVERRIDE >= DESKTOP_DENSITY_MIN
&& DESKTOP_DENSITY_OVERRIDE <= DESKTOP_DENSITY_MAX;
}
+
+ /**
+ * Return {@code true} if desktop mode is unrestricted and is supported in the device.
+ */
+ private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+ }
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 000000000000..2fabd4a33586
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS \ No newline at end of file
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 ef9bf008b294..514307fed4f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -19,7 +19,7 @@ package com.android.wm.shell;
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.internal.protolog.ProtoLog;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 2e5448a9e8d5..b9bf136837a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -29,7 +29,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 5143d419597b..9f01316d5b5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -38,7 +38,7 @@ import android.window.SystemPerformanceHinter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
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 3ded7d246499..f014e559d4b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -24,6 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.CAMERA_CONTROL_STATE_UPDATE;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_APPEARED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_CLICKED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -31,7 +34,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.LocusId;
@@ -52,11 +54,16 @@ import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -75,8 +82,7 @@ import java.util.function.Consumer;
* Unified task organizer for all components in the shell.
* TODO(b/167582004): may consider consolidating this class and TaskOrganizer
*/
-public class ShellTaskOrganizer extends TaskOrganizer implements
- CompatUIController.CompatUICallback {
+public class ShellTaskOrganizer extends TaskOrganizer {
private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
@@ -124,6 +130,15 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
/**
+ * Limited scope callback to notify when a task is removed from the system. This signal is
+ * not synchronized with anything (or any transition), and should not be used in cases where
+ * that is necessary.
+ */
+ public interface TaskVanishedListener {
+ default void onTaskVanished(RunningTaskInfo taskInfo) {}
+ }
+
+ /**
* Callbacks for events on a task with a locus id.
*/
public interface LocusIdListener {
@@ -167,6 +182,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>();
+ // Listeners that should be notified when a task is removed
+ private final ArraySet<TaskVanishedListener> mTaskVanishedListeners = new ArraySet<>();
+
private final Object mLock = new Object();
private StartingWindowController mStartingWindow;
@@ -182,12 +200,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
* In charge of showing compat UI. Can be {@code null} if the device doesn't support size
* compat or if this isn't the main {@link ShellTaskOrganizer}.
*
- * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController},
- * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be
- * initialized with a {@code null} {@link CompatUIController}.
+ * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIHandler},
+ * Subclasses should be initialized with a {@code null} {@link CompatUIHandler}.
*/
@Nullable
- private final CompatUIController mCompatUI;
+ private final CompatUIHandler mCompatUI;
@NonNull
private final ShellCommandHandler mShellCommandHandler;
@@ -211,7 +228,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -223,7 +240,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
protected ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -240,7 +257,21 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
- mCompatUI.setCompatUICallback(this);
+ mCompatUI.setCallback(compatUIEvent -> {
+ switch(compatUIEvent.getEventId()) {
+ case SIZE_COMPAT_RESTART_BUTTON_APPEARED:
+ onSizeCompatRestartButtonAppeared(compatUIEvent.asType());
+ break;
+ case SIZE_COMPAT_RESTART_BUTTON_CLICKED:
+ onSizeCompatRestartButtonClicked(compatUIEvent.asType());
+ break;
+ case CAMERA_CONTROL_STATE_UPDATE:
+ onCameraControlStateUpdated(compatUIEvent.asType());
+ break;
+ default:
+
+ }
+ });
}
registerOrganizer();
}
@@ -409,7 +440,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
/**
- * Removes listener.
+ * Removes a locus id listener.
*/
public void removeLocusIdListener(LocusIdListener listener) {
synchronized (mLock) {
@@ -430,7 +461,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
/**
- * Removes listener.
+ * Removes a focus listener.
*/
public void removeFocusListener(FocusListener listener) {
synchronized (mLock) {
@@ -439,6 +470,24 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
/**
+ * Adds a listener to be notified when a task vanishes.
+ */
+ public void addTaskVanishedListener(TaskVanishedListener listener) {
+ synchronized (mLock) {
+ mTaskVanishedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a task-vanished listener.
+ */
+ public void removeTaskVanishedListener(TaskVanishedListener listener) {
+ synchronized (mLock) {
+ mTaskVanishedListeners.remove(listener);
+ }
+ }
+
+ /**
* Returns a surface which can be used to attach overlays to the home root task
*/
@NonNull
@@ -614,6 +663,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
t.apply();
ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface");
}
+ for (TaskVanishedListener l : mTaskVanishedListeners) {
+ l.onTaskVanished(taskInfo);
+ }
if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
// Preemptively clean up the leash only if shell transitions are not enabled
@@ -647,6 +699,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Shows/hides the given task surface. Not for general use as changing the task visibility may
+ * conflict with other Transitions. This is currently ONLY used to temporarily hide a task
+ * while a drag is in session.
+ */
+ public void setTaskSurfaceVisibility(int taskId, boolean visible) {
+ synchronized (mLock) {
+ final TaskAppearedInfo info = mTasks.get(taskId);
+ if (info != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setVisibility(info.getLeash(), visible);
+ t.apply();
+ }
+ }
+ }
+
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
@@ -694,8 +762,26 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
- @Override
- public void onSizeCompatRestartButtonAppeared(int taskId) {
+ /** Reparents a child window surface to the task surface. */
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ final TaskListener taskListener;
+ synchronized (mLock) {
+ taskListener = mTasks.contains(taskId)
+ ? getTaskListener(mTasks.get(taskId).getTaskInfo())
+ : null;
+ }
+ if (taskListener == null) {
+ ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
+ taskId);
+ return;
+ }
+ taskListener.reparentChildSurfaceToTask(taskId, sc, t);
+ }
+
+ @VisibleForTesting
+ void onSizeCompatRestartButtonAppeared(@NonNull SizeCompatRestartButtonAppeared compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -707,8 +793,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
}
- @Override
- public void onSizeCompatRestartButtonClicked(int taskId) {
+ @VisibleForTesting
+ void onSizeCompatRestartButtonClicked(@NonNull SizeCompatRestartButtonClicked compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -721,8 +808,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
}
- @Override
- public void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state) {
+ @VisibleForTesting
+ void onCameraControlStateUpdated(@NonNull CameraControlStateUpdated compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
+ final int state = compatUIEvent.getState();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -733,22 +822,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
updateCameraCompatControlState(info.getTaskInfo().token, state);
}
- /** Reparents a child window surface to the task surface. */
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- final TaskListener taskListener;
- synchronized (mLock) {
- taskListener = mTasks.contains(taskId)
- ? getTaskListener(mTasks.get(taskId).getTaskInfo())
- : null;
- }
- if (taskListener == null) {
- ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
- taskId);
- return;
- }
- taskListener.reparentChildSurfaceToTask(taskId, sc, t);
- }
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
@@ -777,10 +850,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
// on this Task if there is any.
if (taskListener == null || !taskListener.supportCompatUI()
|| !taskInfo.appCompatTaskInfo.hasCompatUI() || !taskInfo.isVisible) {
- mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, null /* taskListener */));
return;
}
- mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, taskListener));
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
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 a426b206b0cd..5696a544152c 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
@@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
@@ -45,6 +46,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.shared.TransitionUtil;
@@ -263,7 +265,10 @@ class ActivityEmbeddingAnimationRunner {
for (TransitionInfo.Change change : openingChanges) {
final Animation animation =
animationProvider.get(info, change, openingWholeScreenBounds);
- if (animation.getDuration() == 0) {
+ if (shouldUseJumpCutForAnimation(animation)) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ return new ArrayList<>();
+ }
continue;
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -288,7 +293,10 @@ class ActivityEmbeddingAnimationRunner {
}
final Animation animation =
animationProvider.get(info, change, closingWholeScreenBounds);
- if (animation.getDuration() == 0) {
+ if (shouldUseJumpCutForAnimation(animation)) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ return new ArrayList<>();
+ }
continue;
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -398,7 +406,15 @@ class ActivityEmbeddingAnimationRunner {
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
Animation changeAnimation = null;
- Rect parentBounds = new Rect();
+ final Rect parentBounds = new Rect();
+ // We use a single boolean value to record the backdrop override because the override used
+ // for overlay and we restrict to single overlay animation. We should fix the assumption
+ // if we allow multiple overlay transitions.
+ // The backdrop logic is mainly for animations of split animations. The backdrop should be
+ // disabled if there is any open/close target in the same transition as the change target.
+ // However, the overlay change animation usually contains one change target, and shows
+ // backdrop unexpectedly.
+ Boolean overrideShowBackdrop = null;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -421,21 +437,29 @@ class ActivityEmbeddingAnimationRunner {
}
}
- // The TaskFragment may be enter/exit split, so we take the union of both as the parent
- // size.
- parentBounds.union(boundsAnimationChange.getStartAbsBounds());
- parentBounds.union(boundsAnimationChange.getEndAbsBounds());
- if (boundsAnimationChange != change) {
- // Union the change starting bounds in case the activity is resized and reparented
- // to a TaskFragment. In that case, the TaskFragment may not cover the activity's
- // starting bounds.
- parentBounds.union(change.getStartAbsBounds());
+ final TransitionInfo.AnimationOptions options = boundsAnimationChange
+ .getAnimationOptions();
+ if (options != null) {
+ final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions(
+ options, TRANSIT_CHANGE);
+ if (overrideAnimation != null) {
+ overrideShowBackdrop = overrideAnimation.getShowBackdrop();
+ }
}
+ calculateParentBounds(change, boundsAnimationChange, parentBounds);
// There are two animations in the array. The first one is for the start leash
// (snapshot), and the second one is for the end leash (TaskFragment).
- final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
- parentBounds);
+ final Animation[] animations =
+ mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds);
+ // Jump cut if either animation has zero for duration.
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ for (Animation animation : animations) {
+ if (shouldUseJumpCutForAnimation(animation)) {
+ return new ArrayList<>();
+ }
+ }
+ }
// Keep track as we might need to add background color for the animation.
// Although there may be multiple change animation, record one of them is sufficient
// because the background color will be added to the root leash for the whole animation.
@@ -466,7 +490,7 @@ class ActivityEmbeddingAnimationRunner {
// If there is no corresponding open/close window with the change, we should show background
// color to cover the empty part of the screen.
- boolean shouldShouldBackgroundColor = true;
+ boolean shouldShowBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -482,17 +506,26 @@ class ActivityEmbeddingAnimationRunner {
// window without bounds change.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (TransitionUtil.isClosingType(change.getMode())) {
- animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
- shouldShouldBackgroundColor = false;
+ animation =
+ mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds);
+ shouldShowBackgroundColor = false;
} else {
- animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
- shouldShouldBackgroundColor = false;
+ animation =
+ mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds);
+ shouldShowBackgroundColor = false;
+ }
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ if (shouldUseJumpCutForAnimation(animation)) {
+ return new ArrayList<>();
+ }
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
TransitionUtil.getRootFor(change, info)));
}
- if (shouldShouldBackgroundColor && changeAnimation != null) {
+ shouldShowBackgroundColor = overrideShowBackdrop != null
+ ? overrideShowBackdrop : shouldShowBackgroundColor;
+ if (shouldShowBackgroundColor && changeAnimation != null) {
// Change animation may leave part of the screen empty. Show background color to cover
// that.
changeAnimation.setShowBackdrop(true);
@@ -502,6 +535,39 @@ class ActivityEmbeddingAnimationRunner {
}
/**
+ * Calculates parent bounds of the animation target by {@code change}.
+ */
+ @VisibleForTesting
+ static void calculateParentBounds(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) {
+ if (Flags.activityEmbeddingOverlayPresentationFlag()) {
+ final Point endParentSize = change.getEndParentSize();
+ if (endParentSize.equals(0, 0)) {
+ return;
+ }
+ final Point endRelPosition = change.getEndRelOffset();
+ final Point endAbsPosition = new Point(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x,
+ endAbsPosition.y - endRelPosition.y);
+ outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y,
+ parentEndAbsPosition.x + endParentSize.x,
+ parentEndAbsPosition.y + endParentSize.y);
+ } else {
+ // The TaskFragment may be enter/exit split, so we take the union of both as
+ // the parent size.
+ outParentBounds.union(boundsAnimationChange.getStartAbsBounds());
+ outParentBounds.union(boundsAnimationChange.getEndAbsBounds());
+ if (boundsAnimationChange != change) {
+ // Union the change starting bounds in case the activity is resized and
+ // reparented to a TaskFragment. In that case, the TaskFragment may not cover
+ // the activity's starting bounds.
+ outParentBounds.union(change.getStartAbsBounds());
+ }
+ }
+ }
+
+ /**
* Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one.
* The screenshot leash should be attached to the {@code animationChange} surface which we will
* animate later.
@@ -595,6 +661,12 @@ class ActivityEmbeddingAnimationRunner {
return true;
}
+ /** Whether or not to use jump cut based on the animation. */
+ @VisibleForTesting
+ static boolean shouldUseJumpCutForAnimation(@NonNull Animation animation) {
+ return animation.getDuration() == 0;
+ }
+
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
private void prepareForJumpCut(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction) {
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 b9868629e64b..3046307702c2 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
@@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding;
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -27,6 +29,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
+import android.util.Log;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -42,7 +46,6 @@ import com.android.window.flags.Flags;
import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
-// TODO(b/206557124): provide an easier way to customize animation
class ActivityEmbeddingAnimationSpec {
private static final String TAG = "ActivityEmbeddingAnimSpec";
@@ -91,8 +94,14 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is opening in a change transition. */
@NonNull
- Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
- @NonNull Rect parentBounds) {
+ Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return customAnimation;
+ }
+ }
// Use end bounds for opening.
final Rect bounds = change.getEndAbsBounds();
final int startLeft;
@@ -119,8 +128,14 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is closing in a change transition. */
@NonNull
- Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
- @NonNull Rect parentBounds) {
+ Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return customAnimation;
+ }
+ }
// Use start bounds for closing.
final Rect bounds = change.getStartAbsBounds();
final int endTop;
@@ -151,8 +166,17 @@ class ActivityEmbeddingAnimationSpec {
* the second one is for the end leash.
*/
@NonNull
- Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change,
- @NonNull Rect parentBounds) {
+ Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ // TODO(b/293658614): Support more complicated animations that may need more than a noop
+ // animation as the start leash.
+ final Animation noopAnimation = createNoopAnimation(change);
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return new Animation[]{noopAnimation, customAnimation};
+ }
+ }
// Both start bounds and end bounds are in screen coordinates. We will post translate
// to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Rect startBounds = change.getStartAbsBounds();
@@ -203,7 +227,7 @@ class ActivityEmbeddingAnimationSpec {
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
+ final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -230,7 +254,7 @@ class ActivityEmbeddingAnimationSpec {
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
+ final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -263,18 +287,46 @@ class ActivityEmbeddingAnimationSpec {
@Nullable
private Animation loadCustomAnimation(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change, boolean isEnter) {
+ @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) {
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
} else {
options = info.getAnimationOptions();
}
+ return loadCustomAnimationFromOptions(options, mode);
+ }
+
+ @Nullable
+ Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options,
+ @WindowManager.TransitionType int mode) {
if (options == null || options.getType() != ANIM_CUSTOM) {
return null;
}
- final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
- isEnter ? options.getEnterResId() : options.getExitResId());
+ final int resId;
+ if (TransitionUtil.isOpeningType(mode)) {
+ resId = options.getEnterResId();
+ } else if (TransitionUtil.isClosingType(mode)) {
+ resId = options.getExitResId();
+ } else if (mode == TRANSIT_CHANGE) {
+ resId = options.getChangeResId();
+ } else {
+ Log.w(TAG, "Unknown transit type:" + mode);
+ resId = DEFAULT_ANIMATION_RESOURCES_ID;
+ }
+ // Use the default animation if the resources ID is not specified.
+ if (resId == DEFAULT_ANIMATION_RESOURCES_ID) {
+ return null;
+ }
+
+ final Animation anim;
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ // TODO(b/293658614): Consider allowing custom animations from non-default packages.
+ // Enforce limiting to animations from the default "android" package for now.
+ anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
+ } else {
+ anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId);
+ }
if (anim != null) {
return anim;
}
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 7041ea307b0f..ece02711070e 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
@@ -59,7 +59,7 @@ import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
import com.android.wm.shell.R;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 4988a9481d21..e24df0bdc05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -30,7 +30,7 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
+import com.android.internal.jank.InteractionJankMonitor;
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
@@ -86,20 +86,21 @@ public class BackAnimationRunner {
*/
void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
+ InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
final IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
if (shouldMonitorCUJ(apps)) {
- InteractionJankMonitorUtils.endTracing(mCujType);
+ interactionJankMonitor.end(mCujType);
}
finishedCallback.run();
}
};
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
- InteractionJankMonitorUtils.beginTracing(
- mCujType, mContext, apps[0].leash, /* tag */ null);
+ interactionJankMonitor.begin(
+ apps[0].leash, mContext, mCujType);
}
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index a3111b31a2f9..4f04c5c28412 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -48,7 +48,7 @@ import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.policy.SystemBarUtils
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
@@ -324,6 +324,7 @@ abstract class CrossActivityBackAnimation(
enteringHasSameLetterbox = false
lastPostCommitFlingScale = SPRING_SCALE
gestureProgress = 0f
+ triggerBack = false
}
protected fun applyTransform(
@@ -499,10 +500,12 @@ abstract class CrossActivityBackAnimation(
}
override fun onBackCancelled() {
+ triggerBack = false
progressAnimator.onBackCancelled { finishAnimation() }
}
override fun onBackInvoked() {
+ triggerBack = true
progressAnimator.reset()
onGestureCommitted(progressAnimator.velocity)
}
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 381914a58cf2..103a65422504 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
@@ -49,7 +49,7 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.SystemBarUtils;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index c738ce542f8a..e266e2cd7eea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -28,7 +28,7 @@ import android.window.BackMotionEvent
import android.window.BackNavigationInfo
import com.android.internal.R
import com.android.internal.policy.TransitionAnimation
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.annotations.ShellMainThread
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 1279fc42c066..7dbbb04e4406 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
@@ -48,7 +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.internal.protolog.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -894,11 +894,22 @@ public class Bubble implements BubbleViewProvider {
}
@Nullable
- Intent getAppBubbleIntent() {
+ @VisibleForTesting
+ public Intent getAppBubbleIntent() {
return mAppIntent;
}
/**
+ * Sets the intent for a bubble that is an app bubble (one for which {@link #mIsAppBubble} is
+ * true).
+ *
+ * @param appIntent The intent to set for the app bubble.
+ */
+ void setAppBubbleIntent(Intent appIntent) {
+ mAppIntent = appIntent;
+ }
+
+ /**
* Returns whether this bubble is from an app versus a notification.
*/
public boolean isAppBubble() {
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 d2c36e6b637c..e36f6e6c04c5 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
@@ -84,7 +84,7 @@ import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.CollectionUtils;
import com.android.launcher3.icons.BubbleIconFactory;
@@ -1450,6 +1450,8 @@ public class BubbleController implements ConfigurationChangeListener,
if (b != null) {
// It's in the overflow, so remove it & reinflate
mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
+ // Update the bubble entry in the overflow with the latest intent.
+ b.setAppBubbleIntent(intent);
} else {
// App bubble does not exist, lets add and expand it
b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
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 761e02598460..4e6c517b9194 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
@@ -35,7 +35,7 @@ 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.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
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 c7ccd50af550..f7a5c271a729 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
@@ -67,7 +67,7 @@ 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.internal.protolog.ProtoLog;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
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 18e04d14c71b..bf98ef82b475 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
@@ -42,7 +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.protolog.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
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 2382545ab324..0cf187bd9c0f 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
@@ -29,7 +29,7 @@ import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
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 09bec8c37b9a..f93f19d5d1eb 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
@@ -78,7 +78,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
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 0b66bcb6930e..c79d9c4942bf 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
@@ -35,7 +35,7 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 137568458e3c..9429c9e71b3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -29,7 +29,7 @@ import android.view.InputMonitor;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index b7107f09b17f..d4f53ab353ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -28,7 +28,7 @@ import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
/**
* Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
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 aa4129a14dbc..fbef6b5e4a99 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
@@ -38,7 +38,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.internal.protolog.ProtoLog;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.bubbles.BubbleExpandedView;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
index a124f95d7431..c93c11eb2fc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
@@ -20,10 +20,10 @@ import android.app.Activity
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity to create a shortcut to open bubbles */
class CreateBubbleShortcutActivity : Activity() {
@@ -31,7 +31,7 @@ class CreateBubbleShortcutActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Flags.enableRetrievableBubbles()) {
- KtProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
+ ProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
createShortcut()
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
index ae7940ca1b65..e578e9e76979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
@@ -21,9 +21,9 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity that sends a broadcast to open bubbles */
class ShowBubblesActivity : Activity() {
@@ -37,7 +37,7 @@ class ShowBubblesActivity : Activity() {
// Set the package as the receiver is not exported
`package` = packageName
}
- KtProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
+ ProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
sendBroadcast(intent)
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
deleted file mode 100644
index 86f00b83cadd..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.common;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.SurfaceControl;
-import android.view.View;
-
-import com.android.internal.jank.Cuj.CujType;
-import com.android.internal.jank.InteractionJankMonitor;
-
-/** Utils class for simplfy InteractionJank trancing call */
-public class InteractionJankMonitorUtils {
-
- /**
- * Begin a trace session.
- *
- * @param cujType the specific {@link CujType}.
- * @param view the view to trace
- * @param tag the tag to distinguish different flow of same type CUJ.
- */
- public static void beginTracing(@CujType int cujType,
- @NonNull View view, @Nullable String tag) {
- final InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
- if (!TextUtils.isEmpty(tag)) {
- builder.setTag(tag);
- }
- InteractionJankMonitor.getInstance().begin(builder);
- }
-
- /**
- * Begin a trace session.
- *
- * @param cujType the specific {@link CujType}.
- * @param context the context
- * @param surface the surface to trace
- * @param tag the tag to distinguish different flow of same type CUJ.
- */
- public static void beginTracing(@CujType int cujType,
- @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
- final InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
- if (!TextUtils.isEmpty(tag)) {
- builder.setTag(tag);
- }
- InteractionJankMonitor.getInstance().begin(builder);
- }
-
- /**
- * End a trace session.
- *
- * @param cujType the specific {@link CujType}.
- */
- public static void endTracing(@CujType int cujType) {
- InteractionJankMonitor.getInstance().end(cujType);
- }
-
- /**
- * Cancel the trace session.
- *
- * @param cujType the specific {@link CujType}.
- */
- public static void cancelTracing(@CujType int cujType) {
- InteractionJankMonitor.getInstance().cancel(cujType);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
index 81592c35e4ac..e92b0b59d2ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -17,8 +17,8 @@ package com.android.wm.shell.common
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
-import com.android.wm.shell.util.KtProtoLog
/**
* Controller to manage behavior of activities launched with
@@ -30,7 +30,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
var launchAdjacentEnabled: Boolean = true
set(value) {
if (field != value) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
field = value
container?.let { c ->
if (value) {
@@ -52,7 +52,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
* @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
*/
fun setLaunchAdjacentRoot(container: WindowContainerToken) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
this.container = container
if (launchAdjacentEnabled) {
enableContainer(container)
@@ -67,7 +67,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
* @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
*/
fun clearLaunchAdjacentRoot() {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
container?.let {
disableContainer(it)
container = null
@@ -75,14 +75,14 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
}
private fun enableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.setLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
}
private fun disableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.clearLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
index 9e8dfb5f0c6f..a6be64070ac1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -23,9 +23,9 @@ import android.content.pm.PackageManager
import android.os.UserHandle
import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
-import com.android.wm.shell.util.KtProtoLog
import java.util.Arrays
/**
@@ -52,7 +52,7 @@ class MultiInstanceHelper @JvmOverloads constructor(
val packageName = componentName.packageName
for (pkg in staticAppsSupportingMultiInstance) {
if (pkg == packageName) {
- KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
+ ProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
packageName)
return true
}
@@ -70,10 +70,10 @@ class MultiInstanceHelper @JvmOverloads constructor(
// 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)
+ ProtoLog.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",
+ ProtoLog.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) {
@@ -85,10 +85,10 @@ class MultiInstanceHelper @JvmOverloads constructor(
val appProp = packageManager.getProperty(
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
if (appProp.isBoolean) {
- KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
+ ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
return appProp.boolean
} else {
- KtProtoLog.w(WM_SHELL,
+ ProtoLog.w(WM_SHELL,
"Warning: property=%s for application=%s has non-bool type=%d",
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index e261d92bda5c..f7923924789e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -28,7 +28,7 @@ import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index 43c92cab6a68..43f9cb984322 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -32,7 +32,7 @@ import android.util.ArraySet;
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
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 6ffeb97f50fa..8e026f04ac31 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
@@ -27,7 +27,9 @@ import android.util.DisplayMetrics;
import android.util.Size;
import android.view.Gravity;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -39,6 +41,9 @@ public class PipBoundsAlgorithm {
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
+ // The same value (with the same name) is used in Launcher.
+ private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
+
@NonNull private final PipBoundsState mPipBoundsState;
@NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
@NonNull protected final SizeSpecSource mSizeSpecSource;
@@ -206,9 +211,27 @@ public class PipBoundsAlgorithm {
*/
public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
Rect destinationBounds) {
- return sourceRectHint != null
- && sourceRectHint.width() > destinationBounds.width()
- && sourceRectHint.height() > destinationBounds.height();
+ if (sourceRectHint == null || sourceRectHint.isEmpty()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, empty hint");
+ return false;
+ }
+ if (sourceRectHint.width() <= destinationBounds.width()
+ || sourceRectHint.height() <= destinationBounds.height()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, hint(%s) is smaller"
+ + " than destination(%s)", sourceRectHint, destinationBounds);
+ return false;
+ }
+ final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height();
+ final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height();
+ if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, hint(%s) does not match"
+ + " destination(%s) aspect ratio", sourceRectHint, destinationBounds);
+ return false;
+ }
+ return true;
}
public float getDefaultAspectRatio() {
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 7ceaaea3962f..64a1b0c804da 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
@@ -32,7 +32,7 @@ import android.util.ArraySet;
import android.util.Size;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
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
index c421dec025f2..b9c698e5d8b4 100644
--- 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
@@ -26,7 +26,7 @@ import android.window.SystemPerformanceHinter.HighPerfSession;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.annotations.ShellMainThread;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index a09720dd6a70..dcf84d927ad3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -30,10 +30,11 @@ import android.util.Log
import android.util.Pair
import android.util.TypedValue
import android.window.TaskSnapshot
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup
import kotlin.math.abs
+import kotlin.math.roundToInt
/** A class that includes convenience methods. */
object PipUtils {
@@ -149,16 +150,16 @@ object PipUtils {
val appBoundsAspRatio = appBounds.width().toFloat() / appBounds.height()
val width: Int
val height: Int
- var left = 0
- var top = 0
+ var left = appBounds.left
+ var top = appBounds.top
if (appBoundsAspRatio < aspectRatio) {
width = appBounds.width()
- height = Math.round(width / aspectRatio)
- top = (appBounds.height() - height) / 2
+ height = (width / aspectRatio).roundToInt()
+ top = appBounds.top + (appBounds.height() - height) / 2
} else {
height = appBounds.height()
- width = Math.round(height * aspectRatio)
- left = (appBounds.width() - width) / 2
+ width = (height * aspectRatio).roundToInt()
+ left = appBounds.left + (appBounds.width() - width) / 2
}
return Rect(left, top, left + width, top + height)
}
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 c2242a8b87fa..1bc179551825 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
@@ -54,7 +54,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -336,11 +336,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
setTouching();
mStartPos = touchPos;
mMoving = false;
- // This triggers initialization of things like the resize veil in preparation for
- // showing it when the user moves the divider past the slop, and has to be done
- // before onStartDragging() which starts the jank interaction tracing
- mSplitLayout.updateDividerBounds(mSplitLayout.getDividerPosition(),
- false /* shouldUseParallaxEffect */);
mSplitLayout.onStartDragging();
break;
case MotionEvent.ACTION_MOVE:
@@ -497,6 +492,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
return mHideHandle;
}
+ /** Returns true if the divider is currently being physically controlled by the user. */
+ boolean isMoving() {
+ return mMoving;
+ }
+
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
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 de016d3ae400..5097ed8866c9 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
@@ -269,7 +269,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (update) {
if (immediately) {
+ t.setAlpha(mBackgroundLeash, showVeil ? 1f : 0f);
t.setVisibility(mBackgroundLeash, showVeil);
+ t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
t.setVisibility(mIconLeash, showVeil);
} else {
startFadeAnimation(showVeil, false, null);
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 8ced76fd23af..51f9de8305f8 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
@@ -59,7 +59,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -67,7 +68,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -131,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final boolean mDimNonImeSide;
private final boolean mAllowLeftRightSplitInPortrait;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
@@ -163,6 +164,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+ mInteractionJankMonitor = InteractionJankMonitor.getInstance();
resetDividerPosition();
updateInvisibleRect();
}
@@ -569,12 +571,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
void onStartDragging() {
- InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext,
- getDividerLeash(), null /* tag */);
+ mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE);
}
void onDraggingCancelled() {
- InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE);
+ mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_RESIZE);
}
void onDoubleTappedDivider() {
@@ -638,7 +639,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
- InteractionJankMonitorUtils.endTracing(
+ mInteractionJankMonitor.end(
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
@@ -651,9 +652,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
.ofInt(from, to)
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+
+ // If the divider is being physically controlled by the user, we use a cool parallax effect
+ // on the task windows. So if this "snap" animation is an extension of a user-controlled
+ // movement, we pass in true here to continue the parallax effect smoothly.
+ boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null
+ && mSplitWindowManager.getDividerView().isMoving();
+
mDividerFlingAnimator.addUpdateListener(
animation -> updateDividerBounds(
- (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+ (int) animation.getAnimatedValue(),
+ isBeingMovedByUser /* shouldUseParallaxEffect */
+ )
);
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -661,7 +671,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
- InteractionJankMonitorUtils.endTracing(
+ mInteractionJankMonitor.end(
CUJ_SPLIT_SCREEN_RESIZE);
mDividerFlingAnimator = null;
}
@@ -707,8 +717,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
- mContext, getDividerLeash(), null /*tag*/);
+ mInteractionJankMonitor.begin(getDividerLeash(),
+ mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
@@ -716,12 +726,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerPosition = dividerPos;
updateBounds(mDividerPosition);
finishCallback.accept(insets);
- InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mInteractionJankMonitor.end(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
public void onAnimationCancel(Animator animation) {
- InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
});
set.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 5d121c23c6e1..46c1a43f9efe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -37,7 +37,6 @@ import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -192,7 +191,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mDividerView.setInteractive(interactive, hideHandle, from);
}
- View getDividerView() {
+ DividerView getDividerView() {
return mDividerView;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 6781d08c9904..d1b2347a4411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -19,6 +19,16 @@
package com.android.wm.shell.compatui
import android.app.TaskInfo
-fun isSingleTopActivityTranslucent(task: TaskInfo) =
- task.isTopActivityTransparent && task.numActivities == 1
+import android.content.Context
+import com.android.internal.R
+// TODO(b/347289970): Consider replacing with API
+fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
+ isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
+ && !task.isTopActivityStyleFloating)
+
+private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
+ val sysUiPackageName: String =
+ context.resources.getString(R.string.config_systemUi)
+ return task.baseActivity?.packageName == sysUiPackageName
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index bfac24b81d2f..c02c9cf3fd72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -50,6 +49,10 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,17 +74,7 @@ import java.util.function.Predicate;
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
- DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
-
- /** Callback for compat UI interaction. */
- public interface CompatUICallback {
- /** Called when the size compat restart button appears. */
- void onSizeCompatRestartButtonAppeared(int taskId);
- /** Called when the size compat restart button is clicked. */
- void onSizeCompatRestartButtonClicked(int taskId);
- /** Called when the camera compat control state is updated. */
- void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
- }
+ DisplayImeController.ImePositionProcessor, KeyguardChangeListener, CompatUIHandler {
private static final String TAG = "CompatUIController";
@@ -170,7 +163,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final Function<Integer, Integer> mDisappearTimeSupplier;
@Nullable
- private CompatUICallback mCompatUICallback;
+ private Consumer<CompatUIEvent> mCallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
@@ -230,20 +223,21 @@ public class CompatUIController implements OnDisplaysChangedListener,
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for Compat UI interactions. */
- public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
- mCompatUICallback = compatUiCallback;
+ /** Sets the callback for UI interactions. */
+ @Override
+ public void setCallback(@Nullable Consumer<CompatUIEvent> callback) {
+ mCallback = callback;
}
/**
* Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
*
- * @param taskInfo {@link TaskInfo} task the activity is in.
- * @param taskListener listener to handle the Task Surface placement.
+ * @param compatUIInfo {@link CompatUIInfo} encapsulates information about the task and listener
*/
- public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
- @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
+ final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
+ final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
@@ -258,9 +252,15 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
// We're showing the first reachability education so we ignore incoming TaskInfo
- // until the education flow has completed or we double tap.
+ // until the education flow has completed or we double tap. The double-tap
+ // basically cancel all the onboarding flow. We don't have to ignore events in case
+ // the app is in size compat mode.
if (mIsFirstReachabilityEducationRunning) {
- return;
+ if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap
+ && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
+ return;
+ }
+ mIsFirstReachabilityEducationRunning = false;
}
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) {
@@ -278,17 +278,24 @@ public class CompatUIController implements OnDisplaysChangedListener,
final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
&& !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo);
if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) {
- mIsFirstReachabilityEducationRunning = true;
mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId);
- createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
- return;
+ // We activate the first reachability education if the double-tap is enabled.
+ // If the double tap is not enabled (e.g. thin letterbox) we just set the value
+ // of the education being seen.
+ if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ mIsFirstReachabilityEducationRunning = true;
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ return;
+ }
}
}
}
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
- createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ }
// The user aspect ratio button should not be handled when a new TaskInfo is
// sent because of a double tap or when in multi-window mode.
if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
@@ -453,7 +460,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCompatUICallback, taskListener,
+ taskInfo, mSyncQueue, mCallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -465,9 +472,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
taskInfoState.first)) {
// We need to show the dialog
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
- onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+ onCompatInfoChanged(new CompatUIInfo(taskInfoState.first, taskInfoState.second));
} else {
- mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(taskInfoState.first.taskId));
}
}
@@ -562,13 +569,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(stateInfo.first.taskId));
}
private void onRestartDialogDismissCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
- onCompatInfoChanged(stateInfo.first, stateInfo.second);
+ onCompatInfoChanged(new CompatUIInfo(stateInfo.first, stateInfo.second));
}
private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
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 3ab1fad2b203..1931212df548 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
@@ -40,8 +40,10 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
import java.util.function.Consumer;
@@ -50,10 +52,13 @@ import java.util.function.Consumer;
*/
class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
- private final CompatUICallback mCallback;
+ @NonNull
+ private final Consumer<CompatUIEvent> mCallback;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
+ @NonNull
private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
// Remember the last reported states in case visibility changes due to keyguard or IME updates.
@@ -65,6 +70,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
@VisibleForTesting
+ @NonNull
CompatUIHintsState mCompatUIHintsState;
@Nullable
@@ -73,11 +79,15 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
private final float mHideScmTolerance;
- CompatUIWindowManager(Context context, TaskInfo taskInfo,
- SyncTransactionQueue syncQueue, CompatUICallback callback,
- ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
- Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
+ CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull Consumer<CompatUIEvent> callback,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @Nullable DisplayLayout displayLayout,
+ @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>>
+ onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
@@ -122,7 +132,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
updateVisibilityOfViews();
if (mHasSizeCompat) {
- mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+ mCallback.accept(new SizeCompatRestartButtonAppeared(mTaskId));
}
return mLayout;
@@ -177,7 +187,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
: CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, mCameraCompatControlState));
mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
}
@@ -188,7 +198,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
return;
}
mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
- mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED));
mLayout.setCameraControlVisibility(/* show= */ false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
new file mode 100644
index 000000000000..4a0cf9843722
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.compatui.api
+
+/**
+ * Abstraction for all the possible Compat UI Component events.
+ */
+interface CompatUIEvent {
+ /**
+ * Unique event identifier
+ */
+ val eventId: Int
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T : CompatUIEvent> asType(): T? = this as? T
+
+ fun <T : CompatUIEvent> asType(clazz: Class<T>): T? {
+ return if (clazz.isInstance(this)) clazz.cast(this) else null
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
new file mode 100644
index 000000000000..817e554b550e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.compatui.api
+
+import java.util.function.Consumer
+
+/**
+ * Abstraction for the objects responsible to handle all the CompatUI components and the
+ * communication with the server.
+ */
+interface CompatUIHandler {
+ /**
+ * Invoked when a new model is coming from the server.
+ */
+ fun onCompatInfoChanged(compatUIInfo: CompatUIInfo)
+
+ /**
+ * Optional reference to the object responsible to send {@link CompatUIEvent}
+ */
+ fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
new file mode 100644
index 000000000000..dbbf049792f5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.compatui.api
+
+import android.app.TaskInfo
+import com.android.wm.shell.ShellTaskOrganizer
+
+/**
+ * Encapsulate the info of the message from core.
+ */
+data class CompatUIInfo(val taskInfo: TaskInfo, val listener: ShellTaskOrganizer.TaskListener?) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
new file mode 100644
index 000000000000..58ce8ed6c978
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.compatui.impl
+
+import android.app.AppCompatTaskInfo
+import android.app.CameraCompatTaskInfo
+import com.android.wm.shell.compatui.api.CompatUIEvent
+
+internal const val SIZE_COMPAT_RESTART_BUTTON_APPEARED = 0
+internal const val SIZE_COMPAT_RESTART_BUTTON_CLICKED = 1
+internal const val CAMERA_CONTROL_STATE_UPDATE = 2
+
+/**
+ * All the {@link CompatUIEvent} the Compat UI Framework can handle
+ */
+sealed class CompatUIEvents(override val eventId: Int) : CompatUIEvent {
+ /** Sent when the size compat restart button appears. */
+ data class SizeCompatRestartButtonAppeared(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED)
+
+ /** Sent when the size compat restart button is clicked. */
+ data class SizeCompatRestartButtonClicked(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED)
+
+ /** Sent when the camera compat control state is updated. */
+ data class CameraControlStateUpdated(
+ val taskId: Int,
+ @CameraCompatTaskInfo.CameraCompatControlState val state: Int
+ ) : CompatUIEvents(CAMERA_CONTROL_STATE_UPDATE)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
new file mode 100644
index 000000000000..a181eafada7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIEvent
+import com.android.wm.shell.compatui.api.CompatUIHandler
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import java.util.function.Consumer
+
+/**
+ * Default implementation of {@link CompatUIHandler} to handle CompatUI components
+ */
+class DefaultCompatUIHandler : CompatUIHandler {
+
+ private var compatUIEventSender: Consumer<CompatUIEvent>? = null
+ override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
+ // Empty at the moment
+ }
+
+ override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
+ this.compatUIEventSender = compatUIEventSender
+ }
+} \ No newline at end of file
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 609e5af5c5b0..9bdc0b2b55b4 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
@@ -71,6 +71,8 @@ import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -88,12 +90,12 @@ import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.TaskStackTransitionObserver;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -211,7 +213,7 @@ public abstract class WMShellBaseModule {
Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- Optional<CompatUIController> compatUI,
+ Optional<CompatUIHandler> compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -230,7 +232,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<CompatUIController> provideCompatUIController(
+ static Optional<CompatUIHandler> provideCompatUIController(
Context context,
ShellInit shellInit,
ShellController shellController,
@@ -247,6 +249,9 @@ public abstract class WMShellBaseModule {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
+ if (Flags.appCompatUiFramework()) {
+ return Optional.of(new DefaultCompatUIHandler());
+ }
return Optional.of(
new CompatUIController(
context,
@@ -898,7 +903,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopTasksController.flatMap((lazy)-> {
+ return desktopTasksController.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
@@ -917,7 +922,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy)-> {
+ return desktopModeTaskRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
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 87bd84017dee..eeceaa943af2 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -76,10 +77,10 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -220,7 +221,8 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ InteractionJankMonitor interactionJankMonitor) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -237,7 +239,8 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ interactionJankMonitor);
}
return new CaptionWindowDecorViewModel(
context,
@@ -512,6 +515,7 @@ public abstract class WMShellModule {
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DragAndDropController dragAndDropController,
Transitions transitions,
+ KeyguardManager keyguardManager,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@@ -526,7 +530,7 @@ public abstract class WMShellModule {
Optional<RecentTasksController> recentTasksController) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- dragAndDropController, transitions, enterDesktopTransitionHandler,
+ dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
@@ -573,17 +577,18 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
- Transitions transitions) {
- return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
+ return new ToggleResizeDesktopTaskTransitionHandler(transitions, interactionJankMonitor);
}
@WMSingleton
@Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context
- ) {
- return new ExitDesktopTaskTransitionHandler(transitions, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ExitDesktopTaskTransitionHandler(
+ transitions, context, interactionJankMonitor);
}
@WMSingleton
@@ -599,11 +604,12 @@ public abstract class WMShellModule {
Context context,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
Transitions transitions,
+ ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
return desktopModeTaskRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
- context, repository, transitions, shellInit))
+ context, repository, transitions, shellTaskOrganizer, shellInit))
);
}
@@ -642,6 +648,7 @@ public abstract class WMShellModule {
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
+ ShellTaskOrganizer shellTaskOrganizer,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -649,8 +656,8 @@ public abstract class WMShellModule {
Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor) {
return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
- mainExecutor);
+ shellTaskOrganizer, displayController, uiEventLogger, iconProvider,
+ globalDragListener, transitions, mainExecutor);
}
//
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 677fd5deffd3..240cf3b96e89 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
@@ -212,12 +212,13 @@ public abstract class Pip1Module {
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
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
index 9192e6ed3175..400882a8da9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.desktopmode
+import com.android.internal.protolog.ProtoLog
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 {
@@ -27,7 +27,7 @@ class DesktopModeEventLogger {
* entering desktop mode
*/
fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session enter, session: %s reason: %s",
sessionId,
@@ -47,7 +47,7 @@ class DesktopModeEventLogger {
* exiting desktop mode
*/
fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session exit, session: %s reason: %s",
sessionId,
@@ -67,7 +67,7 @@ class DesktopModeEventLogger {
* session id [sessionId]
*/
fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task added, session: %s taskId: %s",
sessionId,
@@ -99,7 +99,7 @@ class DesktopModeEventLogger {
* session id [sessionId]
*/
fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task remove, session: %s taskId: %s",
sessionId,
@@ -131,7 +131,7 @@ class DesktopModeEventLogger {
* having session id [sessionId]
*/
fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
sessionId,
@@ -159,13 +159,24 @@ class DesktopModeEventLogger {
}
companion object {
+ /**
+ * Describes a task position and dimensions.
+ *
+ * @property instanceId instance id of the task
+ * @property uid uid of the app associated with the task
+ * @property taskHeight height of the task in px
+ * @property taskWidth width of the task in px
+ * @property taskX x-coordinate of the top-left corner
+ * @property taskY y-coordinate of the top-left corner
+ *
+ */
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,
+ val taskHeight: Int,
+ val taskWidth: Int,
+ val taskX: Int,
+ val taskY: Int,
)
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 641952b28bfb..066b5ad39d0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -35,7 +35,7 @@ import androidx.core.util.plus
import androidx.core.util.putAll
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
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
@@ -46,11 +46,10 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
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.KtProtoLog
/**
* A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -106,7 +105,7 @@ class DesktopModeLoggerTransitionObserver(
) {
// this was a new recents animation
if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Recents animation running, saving tasks for later"
)
@@ -132,7 +131,7 @@ class DesktopModeLoggerTransitionObserver(
info.flags == 0 &&
tasksSavedForRecents.isNotEmpty()
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Canceled recents animation, restoring tasks"
)
@@ -202,7 +201,7 @@ class DesktopModeLoggerTransitionObserver(
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: taskInfo map after processing changes %s",
postTransitionFreeformTasks.size()
@@ -282,17 +281,23 @@ class DesktopModeLoggerTransitionObserver(
visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
}
- // TODO(b/326231724) - Add logging around taskInfoChanges Updates
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
sessionId: Int,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
) {
- // find new tasks that were added
postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
- if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
- desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+ val currentTaskUpdate = buildTaskUpdateForTask(taskInfo)
+ val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
+ when {
+ // new tasks added
+ previousTaskInfo == null ->
+ desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate)
+ // old tasks that were resized or repositioned
+ // TODO(b/347935387): Log changes only once they are stable.
+ buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate ->
+ desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
}
}
@@ -304,13 +309,17 @@ class DesktopModeLoggerTransitionObserver(
}
}
- // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
- val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
- // add task x, y if available
- taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
-
- return taskUpdate
+ val screenBounds = taskInfo.configuration.windowConfiguration.bounds
+ val positionInParent = taskInfo.positionInParent
+ return TaskUpdate(
+ instanceId = taskInfo.taskId,
+ uid = taskInfo.userId,
+ taskHeight = screenBounds.height(),
+ taskWidth = screenBounds.width(),
+ taskX = positionInParent.x,
+ taskY = positionInParent.y,
+ )
}
/** Get [EnterReason] for this session enter */
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 7d01580ecb6e..ca0586418041 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
@@ -26,8 +26,8 @@ import android.window.WindowContainerToken
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -46,6 +46,9 @@ class DesktopModeTaskRepository {
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
+ // Tasks that are closing, but are still visible
+ // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
+ val closingTasks: ArraySet<Int> = ArraySet(),
// Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
)
@@ -139,7 +142,7 @@ class DesktopModeTaskRepository {
val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
if (added) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add active task=%d displayId=%d",
taskId,
@@ -164,11 +167,47 @@ class DesktopModeTaskRepository {
}
}
if (result) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
}
return result
}
+ /**
+ * Mark a task with given [taskId] as closing on given [displayId]
+ *
+ * @return `true` if the task was not closing on given [displayId]
+ */
+ fun addClosingTask(displayId: Int, taskId: Int): Boolean {
+ val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
+ if (added) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: added closing task=%d displayId=%d",
+ taskId,
+ displayId
+ )
+ }
+ return added
+ }
+
+ /**
+ * Remove task with given [taskId] from closing tasks.
+ *
+ * @return `true` if the task was closing
+ */
+ fun removeClosingTask(taskId: Int): Boolean {
+ var removed = false
+ displayData.forEach { _, data ->
+ if (data.closingTasks.remove(taskId)) {
+ removed = true
+ }
+ }
+ if (removed) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
+ }
+ return removed
+ }
+
/** Check if a task with the given [taskId] was marked as an active task */
fun isActiveTask(taskId: Int): Boolean {
return displayData.valueIterator().asSequence().any { data ->
@@ -176,6 +215,10 @@ class DesktopModeTaskRepository {
}
}
+ /** Check if a task with the given [taskId] was marked as a closing task */
+ fun isClosingTask(taskId: Int): Boolean =
+ displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }
+
/** Whether a task is visible. */
fun isVisibleTask(taskId: Int): Boolean {
return displayData.valueIterator().asSequence().any { data ->
@@ -190,18 +233,27 @@ class DesktopModeTaskRepository {
}
}
- /** Check if a task with the given [taskId] is the only active task on its display */
- fun isOnlyActiveTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.activeTasks.singleOrNull() == taskId
+ /**
+ * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
+ * on its display
+ */
+ fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
+ displayData.valueIterator().asSequence().any { data ->
+ data.visibleTasks
+ .subtract(data.closingTasks)
+ .subtract(data.minimizedTasks)
+ .singleOrNull() == taskId
}
- }
/** Get a set of the active tasks for given [displayId] */
fun getActiveTasks(displayId: Int): ArraySet<Int> {
return ArraySet(displayData[displayId]?.activeTasks)
}
+ /** Returns the minimized tasks for the given [displayId]. */
+ fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
+ ArraySet(displayData[displayId]?.minimizedTasks)
+
/**
* Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
* are visible.
@@ -264,14 +316,14 @@ class DesktopModeTaskRepository {
// Check if count changed
if (prevCount != newCount) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
taskId,
visible,
displayId
)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
prevCount,
@@ -289,7 +341,7 @@ class DesktopModeTaskRepository {
/** Get number of tasks that are marked as visible on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
displayData[displayId]?.visibleTasks?.size ?: 0
@@ -301,7 +353,7 @@ class DesktopModeTaskRepository {
// TODO(b/342417921): Identify if there is additional checks needed to move tasks for
// multi-display scenarios.
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
displayId,
@@ -313,7 +365,7 @@ class DesktopModeTaskRepository {
/** Mark a Task as minimized. */
fun minimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
displayId,
@@ -324,7 +376,7 @@ class DesktopModeTaskRepository {
/** Mark a Task as non-minimized. */
fun unminimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
displayId,
@@ -335,7 +387,7 @@ class DesktopModeTaskRepository {
/** Remove the task from the ordered list. */
fun removeFreeformTask(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
displayId,
@@ -343,7 +395,7 @@ class DesktopModeTaskRepository {
)
displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remaining freeform tasks: %s",
displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 217b1d356122..1bf125938e6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -19,6 +19,8 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
import android.content.pm.ActivityInfo.isFixedOrientationLandscape
import android.content.pm.ActivityInfo.isFixedOrientationPortrait
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -105,7 +107,7 @@ fun calculateInitialBounds(
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
-private fun maximumSizeMaintainingAspectRatio(
+fun maximumSizeMaintainingAspectRatio(
taskInfo: RunningTaskInfo,
targetArea: Size,
aspectRatio: Float
@@ -114,7 +116,8 @@ private fun maximumSizeMaintainingAspectRatio(
val targetWidth = targetArea.width
val finalHeight: Int
val finalWidth: Int
- if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+ // Get orientation either through top activity or task's orientation
+ if (taskInfo.hasPortraitTopActivity()) {
val tempWidth = (targetHeight / aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
@@ -137,7 +140,7 @@ private fun maximumSizeMaintainingAspectRatio(
}
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
-private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
@@ -171,3 +174,41 @@ private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
desiredSize.height + heightOffset
)
}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
+ * entire screen, as area can be offset by left and top start.
+ */
+fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
+ val heightOffset = (areaBounds.height() - desiredSize.height) / 2
+ val widthOffset = (areaBounds.width() - desiredSize.width) / 2
+
+ val newLeft = leftStart + widthOffset
+ val newTop = topStart + heightOffset
+ val newRight = newLeft + desiredSize.width
+ val newBottom = newTop + desiredSize.height
+
+ return Rect(newLeft, newTop, newRight, newBottom)
+}
+
+fun TaskInfo.hasPortraitTopActivity(): Boolean {
+ val topActivityScreenOrientation =
+ topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
+ val appBounds = configuration.windowConfiguration.appBounds
+
+ return when {
+ // First check if activity has portrait screen orientation
+ topActivityScreenOrientation != SCREEN_ORIENTATION_UNSPECIFIED -> {
+ isFixedOrientationPortrait(topActivityScreenOrientation)
+ }
+
+ // Then check if the activity is portrait when letterboxed
+ appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+
+ // Then check if the activity is portrait
+ appBounds != null -> appBounds.height() > appBounds.width()
+
+ // Otherwise just take the orientation of the task
+ else -> isFixedOrientationPortrait(configuration.orientation)
+ }
+}
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 c5111d68881d..18157d6255e3 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
+import android.app.KeyguardManager
import android.app.PendingIntent
import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
@@ -35,12 +36,12 @@ import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
+import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.RemoteTransition
import android.window.TransitionInfo
@@ -49,6 +50,7 @@ import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -65,7 +67,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
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.compatui.isSingleTopActivityTranslucent
+import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -73,9 +75,10 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
-import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
-import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -86,7 +89,6 @@ import com.android.wm.shell.sysui.ShellInit
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.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -108,6 +110,7 @@ class DesktopTasksController(
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
+ private val keyguardManager: KeyguardManager,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
@@ -155,8 +158,6 @@ class DesktopTasksController(
visualIndicator = null
}
}
- private val sysUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
private val transitionAreaHeight
get() =
@@ -185,7 +186,7 @@ class DesktopTasksController(
}
private fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
@@ -199,7 +200,7 @@ class DesktopTasksController(
recentsTransitionHandler.addTransitionStateListener(
object : RecentsTransitionStateListener {
override fun onAnimationStateChanged(running: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: recents animation state changed running=%b",
running
@@ -216,11 +217,6 @@ class DesktopTasksController(
return visualIndicator
}
- // TODO(b/347289970): Consider replacing with API
- private fun isSystemUIApplication(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.baseActivity?.packageName == sysUIPackageName
- }
-
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -235,7 +231,7 @@ class DesktopTasksController(
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(displayId, wct)
@@ -286,7 +282,7 @@ class DesktopTasksController(
moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource)
}
else -> {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, expected less " +
"than 3 focused tasks but found %d",
@@ -316,7 +312,7 @@ class DesktopTasksController(
transitionSource: DesktopModeTransitionSource,
): Boolean {
recentTasksController?.findTaskInBackground(taskId)?.let {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d",
taskId
@@ -348,23 +344,16 @@ class DesktopTasksController(
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
) {
- if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
- KtProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: Cannot enter desktop, " +
- "translucent top activity found. This is likely a modal dialog."
- )
- return
- }
- if (isSystemUIApplication(task)) {
- KtProtoLog.w(
+ if (Flags.enableDesktopWindowingModalsPolicy()
+ && isTopActivityExemptFromDesktopWindowing(context, task)) {
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, " +
- "systemUI top activity found."
+ "ineligible top activity found."
)
return
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
@@ -391,7 +380,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: startDragToDesktop taskId=%d",
taskInfo.taskId
@@ -407,14 +396,14 @@ class DesktopTasksController(
* [startDragToDesktop].
*/
private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTaskToFront(wct)
+ moveHomeTask(wct, toTop = true)
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
@@ -442,12 +431,21 @@ class DesktopTasksController(
* active task.
*
* @param wct transaction to modify if the last active task is closed
+ * @param displayId display id of the window that's being closed
* @param taskId task id of the window that's being closed
*/
- fun onDesktopWindowClose(wct: WindowContainerTransaction, taskId: Int) {
- if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) {
+ fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) {
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) {
removeWallpaperActivity(wct)
}
+ if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
+ // Could happen if the task hasn't been removed from closing list after it disappeared
+ ProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: the task with taskId=%d is already closing!",
+ taskId
+ )
+ }
}
/** Move a task with given `taskId` to fullscreen */
@@ -466,7 +464,7 @@ class DesktopTasksController(
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToSplit taskId=%d",
task.taskId
@@ -499,7 +497,7 @@ class DesktopTasksController(
* [startDragToDesktop].
*/
fun cancelDragToDesktop(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: cancelDragToDesktop taskId=%d",
task.taskId
@@ -514,7 +512,7 @@ class DesktopTasksController(
position: Point,
transitionSource: DesktopModeTransitionSource
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToFullscreen with animation taskId=%d",
task.taskId
@@ -542,7 +540,7 @@ class DesktopTasksController(
/** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveTaskToFront taskId=%d",
taskInfo.taskId
@@ -573,10 +571,10 @@ class DesktopTasksController(
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (task == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
return
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToNextDisplay: taskId=%d taskDisplayId=%d",
taskId,
@@ -591,7 +589,7 @@ class DesktopTasksController(
newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
}
if (newDisplayId == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
return
}
moveToDisplay(task, newDisplayId)
@@ -603,7 +601,7 @@ class DesktopTasksController(
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToDisplay: taskId=%d displayId=%d",
task.taskId,
@@ -611,13 +609,13 @@ class DesktopTasksController(
)
if (task.displayId == displayId) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
return
}
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
return
}
@@ -638,13 +636,21 @@ class DesktopTasksController(
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
val destinationBounds = Rect()
- if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
- // The desktop task is currently occupying the whole stable bounds. If the bounds
- // before the task was toggled to stable bounds were saved, toggle the task to those
- // bounds. Otherwise, toggle to the default bounds.
+
+ val isMaximized = if (taskInfo.isResizeable) {
+ currentTaskBounds == stableBounds
+ } else {
+ currentTaskBounds.width() == stableBounds.width()
+ || currentTaskBounds.height() == stableBounds.height()
+ }
+
+ if (isMaximized) {
+ // The desktop task is at the maximized width and/or height of the stable bounds.
+ // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
+ // Otherwise, toggle to the default bounds.
val taskBoundsBeforeMaximize =
desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
if (taskBoundsBeforeMaximize != null) {
@@ -659,9 +665,20 @@ class DesktopTasksController(
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
- val taskBounds = taskInfo.configuration.windowConfiguration.bounds
- desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds)
- destinationBounds.set(stableBounds)
+ desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
+
+ if (taskInfo.isResizeable) {
+ // if resizable then expand to entire stable bounds (full display minus insets)
+ destinationBounds.set(stableBounds)
+ } else {
+ // if non-resizable then calculate max bounds according to aspect ratio
+ val activityAspectRatio = calculateAspectRatio(taskInfo)
+ val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+ Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+ val newBounds = centerInArea(
+ newSize, stableBounds, stableBounds.left, stableBounds.top)
+ destinationBounds.set(newBounds)
+ }
}
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -753,7 +770,7 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
newTaskIdInFront ?: "null"
@@ -764,7 +781,7 @@ class DesktopTasksController(
addWallpaperActivity(wct)
} else {
// Move home to front
- moveHomeTaskToFront(wct)
+ moveHomeTask(wct, toTop = true)
}
val nonMinimizedTasksOrderedFrontToBack =
@@ -790,15 +807,15 @@ class DesktopTasksController(
return taskToMinimize
}
- private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+ private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
shellTaskOrganizer
.getRunningTasks(context.displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
@@ -818,7 +835,7 @@ class DesktopTasksController(
private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
desktopModeTaskRepository.wallpaperActivityToken?.let { token ->
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
wct.removeTask(token)
}
}
@@ -856,7 +873,7 @@ class DesktopTasksController(
transition: IBinder,
request: TransitionRequestInfo
): WindowContainerTransaction? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest request=%s",
request
@@ -870,8 +887,8 @@ class DesktopTasksController(
reason = "recents animation is running"
false
}
- // Handle back navigation for the last window if wallpaper available
- shouldRemoveWallpaper(request) -> true
+ // Handle task closing for the last window if wallpaper is available
+ shouldHandleTaskClosing(request) -> true
// Only handle open or to front transitions
request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
reason = "transition type not handled (${request.type})"
@@ -898,7 +915,7 @@ class DesktopTasksController(
}
if (!shouldHandleRequest) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: skipping handleRequest reason=%s",
reason
@@ -909,11 +926,10 @@ class DesktopTasksController(
val result =
triggerTask?.let { task ->
when {
- request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
- // Check if the task has a top transparent activity
- shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task)
- // Check if the task has a top systemUI activity
- isSystemUIApplication(task) -> handleIncompatibleTaskLaunch(task)
+ // Check if the closing task needs to be handled
+ TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
+ // Check if the top task shouldn't be allowed to enter desktop mode
+ isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
// Check if fullscreen task should be updated
task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
// Check if freeform task should be updated
@@ -923,7 +939,7 @@ class DesktopTasksController(
}
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest result=%s",
result ?: "null"
@@ -947,28 +963,31 @@ class DesktopTasksController(
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
- // TODO(b/347289970): Consider replacing with API
- private fun shouldLaunchAsModal(task: TaskInfo) =
- Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
+ private fun isIncompatibleTask(task: TaskInfo) =
+ Flags.enableDesktopWindowingModalsPolicy()
+ && isTopActivityExemptFromDesktopWindowing(context, task)
- private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
+ private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
return Flags.enableDesktopWindowingWallpaperActivity() &&
- request.type == TRANSIT_TO_BACK &&
- request.triggerTask?.let { task ->
- desktopModeTaskRepository.isOnlyActiveTask(task.taskId)
- }
- ?: false
+ TransitionUtil.isClosingType(request.type) &&
+ request.triggerTask != null
}
private fun handleFreeformTaskLaunch(
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ if (keyguardManager.isKeyguardLocked) {
+ // Do NOT handle freeform task launch when locked.
+ // It will be launched in fullscreen windowing mode (Details: b/160925539)
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ return null
+ }
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch freeform task to fullscreen oon transition" +
+ "DesktopTasksController: bring desktop tasks to front on transition" +
" taskId=%d",
task.taskId
)
@@ -995,9 +1014,9 @@ class DesktopTasksController(
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
" taskId=%d",
@@ -1005,6 +1024,9 @@ class DesktopTasksController(
)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
+ // In some launches home task is moved behind new task being launched. Make sure
+ // that's not the case for launches in desktop.
+ moveHomeTask(wct, toTop = false)
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
@@ -1024,17 +1046,26 @@ class DesktopTasksController(
return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) }
}
- /** Handle back navigation by removing wallpaper activity if it's the last active task */
- private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? {
- if (
- desktopModeTaskRepository.isOnlyActiveTask(task.taskId) &&
+ /** Handle task closing by removing wallpaper activity if it's the last active task */
+ private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
+ val wct = if (
+ desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) &&
desktopModeTaskRepository.wallpaperActivityToken != null
) {
// Remove wallpaper activity when the last active task is removed
- return WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
+ WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
} else {
- return null
+ null
+ }
+ if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
+ // Could happen if the task hasn't been removed from closing list after it disappeared
+ ProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: the task with taskId=%d is already closing!",
+ task.taskId
+ )
}
+ return wct
}
private fun addMoveToDesktopChanges(
@@ -1367,7 +1398,7 @@ class DesktopTasksController(
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")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
return false
}
@@ -1458,7 +1489,7 @@ class DesktopTasksController(
private val listener: VisibleTasksListener =
object : VisibleTasksListener {
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
@@ -1503,11 +1534,11 @@ class DesktopTasksController(
}
override fun stashDesktopApps(displayId: Int) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
}
override fun hideStashedDesktopApps(displayId: Int) {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: hideStashedDesktopApps is deprecated"
)
@@ -1534,7 +1565,7 @@ class DesktopTasksController(
}
override fun setTaskListener(listener: IDesktopTaskListener?) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: set task listener=%s",
listener ?: "null"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 0f88384ec2ac..534cc22ada47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -23,12 +23,12 @@ import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
-import com.android.wm.shell.util.KtProtoLog
/**
* Limits the number of tasks shown in Desktop Mode.
@@ -42,9 +42,12 @@ class DesktopTasksLimiter (
private val shellTaskOrganizer: ShellTaskOrganizer,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
+ @VisibleForTesting
+ val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
init {
transitions.registerObserver(minimizeTransitionObserver)
+ taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
}
private data class TaskDetails (val displayId: Int, val taskId: Int)
@@ -68,7 +71,7 @@ class DesktopTasksLimiter (
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: task %d is not reordered to back nor invis",
taskToMinimize.taskId)
@@ -106,19 +109,48 @@ class DesktopTasksLimiter (
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: transition %s finished", transition)
mPendingTransitionTokensAndTasks.remove(transition)
}
}
+ @VisibleForTesting
+ inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+ override fun onActiveTasksChanged(displayId: Int) {
+ val wct = WindowContainerTransaction()
+ removeLeftoverMinimizedTasks(displayId, wct)
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+
+ fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
+ if (taskRepository
+ .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) {
+ return
+ }
+ val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
+ if (remainingMinimizedTasks.isEmpty()) {
+ return
+ }
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksLimiter: removing leftover minimized tasks: $remainingMinimizedTasks")
+ remainingMinimizedTasks.forEach { taskIdToRemove ->
+ val taskToRemove = shellTaskOrganizer.getRunningTaskInfo(taskIdToRemove)
+ if (taskToRemove != null) {
+ wct.removeTask(taskToRemove.token)
+ }
+ }
+ }
+ }
+
/**
* Mark a task as minimized, this should only be done after the corresponding transition has
* finished so we don't minimize the task if the transition fails.
*/
private fun markTaskMinimized(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: marking %d as minimized", taskId)
taskRepository.minimizeTask(displayId, taskId)
@@ -137,7 +169,7 @@ class DesktopTasksLimiter (
wct: WindowContainerTransaction,
newFrontTaskInfo: RunningTaskInfo,
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
newFrontTaskInfo.taskId)
@@ -185,7 +217,7 @@ class DesktopTasksLimiter (
visibleFreeformTaskIdsOrderedFrontToBack: List<Int>
): RunningTaskInfo? {
if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: no need to minimize; tasks below limit")
// No need to minimize anything
@@ -195,7 +227,7 @@ class DesktopTasksLimiter (
shellTaskOrganizer.getRunningTaskInfo(
visibleFreeformTaskIdsOrderedFrontToBack.last())
if (taskToMinimize == null) {
- KtProtoLog.e(
+ ProtoLog.e(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: taskToMinimize == null")
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index dae75f90e3ae..246fd9281975 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -21,35 +21,38 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
/**
- * A [Transitions.TransitionObserver] that observes shell transitions and updates
- * the [DesktopModeTaskRepository] state TODO: b/332682201
- * This observes transitions related to desktop mode
- * and other transitions that originate both within and outside shell.
+ * A [Transitions.TransitionObserver] that observes shell transitions and updates the
+ * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
context: Context,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val transitions: Transitions,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
) : Transitions.TransitionObserver {
init {
- if (Transitions.ENABLE_SHELL_TRANSITIONS &&
- DesktopModeStatus.canEnterDesktopMode(context)) {
+ if (
+ Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context)
+ ) {
shellInit.addInitCallback(::onInit, this)
}
}
fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
transitions.registerObserver(this)
}
@@ -83,8 +86,16 @@ class DesktopTasksTransitionObserver(
change.taskInfo?.let { taskInfo ->
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
- WindowManager.TRANSIT_OPEN ->
+ WindowManager.TRANSIT_OPEN -> {
desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ // After the task for the wallpaper is created, set it non-trimmable.
+ // This is important to prevent recents from trimming and removing the
+ // task.
+ shellTaskOrganizer.applyTransaction(
+ WindowContainerTransaction()
+ .setTaskTrimmableFromRecents(taskInfo.token, false)
+ )
+ }
WindowManager.TRANSIT_CLOSE ->
desktopModeTaskRepository.wallpaperActivityToken = null
else -> {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index c4a4474689fa..1c2415c236ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -21,8 +21,8 @@ import android.app.ActivityManager
import android.content.ComponentName
import android.os.Bundle
import android.view.WindowManager
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
/**
* A transparent activity used in the desktop mode to show the wallpaper under the freeform windows.
@@ -36,7 +36,7 @@ import com.android.wm.shell.util.KtProtoLog
class DesktopWallpaperActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
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 d99b724c936f..ddee8fac8f44 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
@@ -29,6 +29,7 @@ import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
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
@@ -42,7 +43,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.transition.Transitions.TransitionHandler
-import com.android.wm.shell.util.KtProtoLog
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
@@ -114,7 +114,7 @@ class DragToDesktopTransitionHandler(
dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: Drag to desktop transition already in progress."
)
@@ -599,7 +599,7 @@ class DragToDesktopTransitionHandler(
) {
val state = transitionState ?: return
if (aborted && state.startTransitionToken == transition) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: onTransitionConsumed() start transition aborted"
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 891f75cfdbda..171378f9a164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -42,6 +42,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -60,6 +62,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
private final Context mContext;
private final Transitions mTransitions;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -67,17 +70,21 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context) {
- this(transitions, SurfaceControl.Transaction::new, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
- Context context) {
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
+ mInteractionJankMonitor = interactionJankMonitor;
}
/**
@@ -146,6 +153,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
final int screenHeight = metrics.heightPixels;
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
+ mInteractionJankMonitor
+ .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
@@ -175,6 +184,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
if (mOnAnimationFinishedCallback != null) {
mOnAnimationFinishedCallback.accept(finishT);
}
+ mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
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 88d0554669b7..c35d77a1d74a 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
@@ -27,6 +27,8 @@ import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
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.OnTaskResizeAnimationListener
@@ -35,7 +37,8 @@ import java.util.function.Supplier
/** Handles the animation of quick resizing of desktop tasks. */
class ToggleResizeDesktopTaskTransitionHandler(
private val transitions: Transitions,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val interactionJankMonitor: InteractionJankMonitor
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
@@ -44,8 +47,9 @@ class ToggleResizeDesktopTaskTransitionHandler(
private var boundsAnimator: Animator? = null
constructor(
- transitions: Transitions
- ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+ transitions: Transitions,
+ interactionJankMonitor: InteractionJankMonitor
+ ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/** Starts a quick resize transition. */
fun startTransition(wct: WindowContainerTransaction) {
@@ -103,6 +107,8 @@ class ToggleResizeDesktopTaskTransitionHandler(
onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
boundsAnimator = null
+ interactionJankMonitor.end(
+ Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
}
)
addUpdateListener { anim ->
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 438aa768165e..3572d161f5b9 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
@@ -73,7 +73,7 @@ stack traces when specific surface transaction calls are made, which is possible
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_call setAlpha,setPosition # matches the name of the SurfaceControlTransaction methods
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"
@@ -87,7 +87,17 @@ 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 activity starts in the app process
+It can sometimes be useful to trace specific logs and when they are applied (sometimes we build
+transactions that can be applied later). You can do this by adding the "merge" and "apply" calls to
+the set of requested calls:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,merge,apply # apply will dump logs of each setAlpha or merge call on that tx
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+```
+
+## Tracing activity starts & finishes 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
@@ -103,6 +113,19 @@ adb shell setprop persist.wm.debug.start_activity \"\"
adb reboot
```
+Likewise, to trace where a finish() call may be made in the app process, you can enable this system
+property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.finish_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.finish_activity \"\"
+adb reboot
+```
+
## Dumps
Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
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 c374eb8e8f03..e00353d6ac82 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
@@ -52,6 +52,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -59,9 +60,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
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.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -85,6 +87,7 @@ import java.util.function.Function;
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
GlobalDragListener.GlobalDragListenerCallback,
DisplayController.OnDisplaysChangedListener,
+ ShellTaskOrganizer.TaskVanishedListener,
View.OnDragListener, ComponentCallbacks2 {
private static final String TAG = DragAndDropController.class.getSimpleName();
@@ -92,6 +95,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
@@ -133,6 +137,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
+ ShellTaskOrganizer shellTaskOrganizer,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -142,6 +147,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mContext = context;
mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
+ mShellTaskOrganizer = shellTaskOrganizer;
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
@@ -163,6 +169,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}, 0);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
+ mShellTaskOrganizer.addTaskVanishedListener(this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mGlobalDragListener.setListener(this);
}
@@ -281,6 +288,34 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
@Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo.baseIntent == null) {
+ // Invalid info
+ return;
+ }
+ // Find the active drag
+ PerDisplay pd = null;
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ final PerDisplay iPd = mDisplayDropTargets.valueAt(i);
+ if (iPd.isHandlingDrag) {
+ pd = iPd;
+ break;
+ }
+ }
+ if (pd == null || pd.activeDragCount <= 0 || !pd.isHandlingDrag) {
+ // Not currently dragging
+ return;
+ }
+
+ // Update the drag session
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Handling vanished task: id=%d component=%s", taskInfo.taskId,
+ taskInfo.baseIntent.getComponent());
+ pd.dragSession.updateRunningTask();
+ pd.dragLayout.updateSession(pd.dragSession);
+ }
+
+ @Override
public boolean onDrag(View target, DragEvent event) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
@@ -298,9 +333,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
+ "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
pd.isHandlingDrag, event.getClipData().getItemCount(),
- DragUtils.getMimeTypesConcatenated(description));
+ DragUtils.getMimeTypesConcatenated(description),
+ DragUtils.dragFlagsToString(event.getDragFlags()));
}
if (!pd.isHandlingDrag) {
@@ -313,13 +349,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
- // TODO(b/290391688): Also update the session data with task stack changes
pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData(),
event.getDragFlags());
- pd.dragSession.update();
+ pd.dragSession.initialize();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Hiding task surface: taskId=%d", pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, false /* visible */);
+ }
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners(l -> {
l.onDragStarted();
@@ -349,6 +390,13 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
if (pd.dragLayout.hasDropped()) {
mLogger.logDrop();
} else {
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Re-showing task surface: taskId=%d",
+ pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, true /* visible */);
+ }
pd.activeDragCount--;
pd.dragLayout.hide(event, () -> {
if (pd.activeDragCount == 0) {
@@ -402,7 +450,16 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private boolean handleDrop(DragEvent event, PerDisplay pd) {
final SurfaceControl dragSurface = event.getDragSurface();
pd.activeDragCount--;
- return pd.dragLayout.drop(event, dragSurface, () -> {
+ // Find the token of the task to hide as a part of entering split
+ WindowContainerToken hideTaskToken = null;
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ActivityManager.RunningTaskInfo info = mShellTaskOrganizer.getRunningTaskInfo(
+ pd.dragSession.hideDragSourceTaskId);
+ if (info != null) {
+ hideTaskToken = info.token;
+ }
+ }
+ return pd.dragLayout.drop(event, dragSurface, hideTaskToken, () -> {
if (pd.activeDragCount == 0) {
// Hide the window if another drag hasn't been started while animating the drop
setDropTargetWindowVisibility(pd, View.INVISIBLE);
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 a42ca1905ee7..95fe8b6f1f4e 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
@@ -59,6 +59,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.window.WindowContainerToken;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -66,7 +67,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -84,7 +85,10 @@ public class DragAndDropPolicy {
private static final String TAG = DragAndDropPolicy.class.getSimpleName();
private final Context mContext;
- private final Starter mStarter;
+ // Used only for launching a fullscreen task (or as a fallback if there is no split starter)
+ private final Starter mFullscreenStarter;
+ // Used for launching tasks into splitscreen
+ private final Starter mSplitscreenStarter;
private final SplitScreenController mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
private final RectF mDisallowHitRegion = new RectF();
@@ -97,10 +101,12 @@ public class DragAndDropPolicy {
}
@VisibleForTesting
- DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) {
+ DragAndDropPolicy(Context context, SplitScreenController splitScreen,
+ Starter fullscreenStarter) {
mContext = context;
mSplitScreen = splitScreen;
- mStarter = mSplitScreen != null ? mSplitScreen : starter;
+ mFullscreenStarter = fullscreenStarter;
+ mSplitscreenStarter = splitScreen;
}
/**
@@ -229,8 +235,13 @@ public class DragAndDropPolicy {
return null;
}
+ /**
+ * Handles the drop on a given {@param target}. If a {@param hideTaskToken} is set, then the
+ * handling of the drop will attempt to hide the given task as a part of the same window
+ * container transaction if possible.
+ */
@VisibleForTesting
- void handleDrop(Target target) {
+ void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -245,17 +256,21 @@ public class DragAndDropPolicy {
mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
+ final Starter starter = target.type == TYPE_FULLSCREEN
+ ? mFullscreenStarter
+ : mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, position);
+ launchApp(mSession, starter, position, hideTaskToken);
} else {
- launchIntent(mSession, position);
+ launchIntent(mSession, starter, position, hideTaskToken);
}
}
/**
* Launches an app provided by SysUI.
*/
- private void launchApp(DragSession session, @SplitPosition int position) {
+ private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
position);
final ClipDescription description = session.getClipDescription();
@@ -275,11 +290,15 @@ public class DragAndDropPolicy {
if (isTask) {
final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
- mStarter.startTask(taskId, position, opts);
+ starter.startTask(taskId, position, opts, hideTaskToken);
} else if (isShortcut) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Can not hide task token with starting shortcut");
+ }
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);
+ starter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent =
session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
@@ -288,15 +307,16 @@ public class DragAndDropPolicy {
Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
}
}
- mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
- position, opts);
+ starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
+ position, opts, hideTaskToken);
}
}
/**
* Launches an intent sender provided by an application.
*/
- private void launchIntent(DragSession session, @SplitPosition int position) {
+ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -309,20 +329,22 @@ public class DragAndDropPolicy {
| FLAG_ACTIVITY_MULTIPLE_TASK);
final Bundle opts = baseActivityOpts.toBundle();
- mStarter.startIntent(session.launchableIntent,
+ starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
- null /* fillIntent */, position, opts);
+ null /* fillIntent */, position, opts, hideTaskToken);
}
/**
* Interface for actually committing the task launches.
*/
public interface Starter {
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options);
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options);
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -344,7 +366,12 @@ public class DragAndDropPolicy {
}
@Override
- public void startTask(int taskId, int position, @Nullable Bundle options) {
+ public void startTask(int taskId, int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
@@ -367,7 +394,12 @@ public class DragAndDropPolicy {
@Override
public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- int position, @Nullable Bundle options) {
+ int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
@@ -420,7 +452,7 @@ public class DragAndDropPolicy {
@Override
public String toString() {
- return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}";
+ return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion + "}";
}
}
}
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 4bb10dfdf8c6..e4aa115347eb 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
@@ -20,7 +20,6 @@ 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_REGION;
@@ -42,6 +41,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
@@ -51,11 +51,13 @@ import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -102,6 +104,8 @@ public class DragLayout extends LinearLayout
private boolean mIsShowing;
private boolean mHasDropped;
private DragSession mSession;
+ // The last position that was handled by the drag layout
+ private final Point mLastPosition = new Point();
@SuppressLint("WrongConstant")
public DragLayout(Context context, SplitScreenController splitScreenController,
@@ -265,6 +269,15 @@ public class DragLayout extends LinearLayout
*/
public void prepare(DragSession session, InstanceId loggerSessionId) {
mPolicy.start(session, loggerSessionId);
+ updateSession(session);
+ }
+
+ /**
+ * Updates the drag layout based on the diven drag session.
+ */
+ public void updateSession(DragSession session) {
+ // Note: The policy currently just keeps a reference to the session
+ boolean updatingExistingSession = mSession != null;
mSession = session;
mHasDropped = false;
mCurrentTarget = null;
@@ -280,6 +293,8 @@ public class DragLayout extends LinearLayout
int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
+ mDropZoneView1.setForceIgnoreBottomMargin(false);
+ mDropZoneView2.setForceIgnoreBottomMargin(false);
updateDropZoneSizes(null, null); // passing null splits the views evenly
} else {
// We use the first drop zone to show the fullscreen highlight, and don't need
@@ -312,6 +327,11 @@ public class DragLayout extends LinearLayout
updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
requestLayout();
+ if (updatingExistingSession) {
+ // Update targets if we are already currently dragging
+ recomputeDropTargets();
+ update(mLastPosition.x, mLastPosition.y);
+ }
}
private void updateDropZoneSizesForSingleTask() {
@@ -359,6 +379,9 @@ public class DragLayout extends LinearLayout
mDropZoneView2.setLayoutParams(dropZoneView2);
}
+ /**
+ * Shows the drag layout.
+ */
public void show() {
mIsShowing = true;
recomputeDropTargets();
@@ -384,13 +407,19 @@ public class DragLayout extends LinearLayout
* Updates the visible drop target as the user drags.
*/
public void update(DragEvent event) {
+ update((int) event.getX(), (int) event.getY());
+ }
+
+ /**
+ * Updates the visible drop target as the user drags to the given coordinates.
+ */
+ private void update(int x, int y) {
if (mHasDropped) {
return;
}
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
- DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
- (int) event.getX(), (int) event.getY());
+ DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y);
if (mCurrentTarget != target) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
if (target == null) {
@@ -429,6 +458,7 @@ public class DragLayout extends LinearLayout
}
mCurrentTarget = target;
}
+ mLastPosition.set(x, y);
}
/**
@@ -436,6 +466,7 @@ public class DragLayout extends LinearLayout
*/
public void hide(DragEvent event, Runnable hideCompleteCallback) {
mIsShowing = false;
+ mLastPosition.set(-1, -1);
animateSplitContainers(false, () -> {
if (hideCompleteCallback != null) {
hideCompleteCallback.run();
@@ -456,13 +487,13 @@ public class DragLayout extends LinearLayout
/**
* Handles the drop onto a target and animates out the visible drop targets.
*/
- public boolean drop(DragEvent event, SurfaceControl dragSurface,
- Runnable dropCompleteCallback) {
+ public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
+ @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
final boolean handledDrop = mCurrentTarget != null;
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget);
+ mPolicy.handleDrop(mCurrentTarget, hideTaskToken);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -472,7 +503,7 @@ public class DragLayout extends LinearLayout
return handledDrop;
}
- private void hideDragSurface(SurfaceControl dragSurface) {
+ private void hideDragSurface(@NonNull SurfaceControl dragSurface) {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
// Currently the splash icon animation runs with the default ValueAnimator duration of
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 0addd432aff0..dcbdfa349687 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -27,10 +28,13 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.PersistableBundle;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
@@ -61,6 +65,7 @@ public class DragSession {
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
+ int hideDragSourceTaskId = -1;
DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data, int dragFlags) {
@@ -68,6 +73,11 @@ public class DragSession {
mInitialDragData = data;
mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
+ hideDragSourceTaskId = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
+ : -1;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Extracting drag source taskId: taskId=%d", hideDragSourceTaskId);
}
/**
@@ -79,17 +89,38 @@ public class DragSession {
}
/**
- * Updates the session data based on the current state of the system.
+ * Updates the running task for this drag session.
*/
- void update() {
- List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ void updateRunningTask() {
+ final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
+ final List<ActivityManager.RunningTaskInfo> tasks =
+ mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
+ false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Skipping running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ continue;
+ }
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ break;
+ }
}
+ }
+
+ /**
+ * Updates the session data based on the current state of the system at the start of the drag.
+ */
+ void initialize() {
+ updateRunningTask();
activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
// TODO: This should technically check & respect config_supportsNonResizableMultiWindow
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 e215870f1894..22cfa328bfda 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
@@ -19,16 +19,28 @@ package com.android.wm.shell.draganddrop;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.view.View.DRAG_FLAG_ACCESSIBILITY_ACTION;
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_READ;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_WRITE;
+import static android.view.View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START;
+import static android.view.View.DRAG_FLAG_OPAQUE;
+import static android.view.View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION;
+import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.StringJoiner;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -76,7 +88,7 @@ public class DragUtils {
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) {
- if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
+ if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
// Disallow launching the intent if the app does not want to delegate it to the system
return null;
}
@@ -105,4 +117,35 @@ public class DragUtils {
}
return mimeTypes;
}
+
+ /**
+ * Returns the string description of the given {@param dragFlags}.
+ */
+ public static String dragFlagsToString(int dragFlags) {
+ StringJoiner str = new StringJoiner("|");
+ if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) {
+ str.add("GLOBAL");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
+ str.add("GLOBAL_URI_READ");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
+ str.add("GLOBAL_URI_WRITE");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PERSISTABLE_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PREFIX_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
+ str.add("OPAQUE");
+ } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ str.add("ACCESSIBILITY_ACTION");
+ } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
+ str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
+ str.add("GLOBAL_SAME_APPLICATION");
+ } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG");
+ } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ str.add("HIDE_CALLING_TASK_ON_DRAG_START");
+ }
+ return str.toString();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 724a130ef52d..18cd2d84d32d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.draganddrop;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -37,13 +38,16 @@ import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* Renders a drop zone area for items being dragged.
*/
public class DropZoneView extends FrameLayout {
+ private static final boolean DEBUG_LAYOUT = false;
private static final float SPLASHSCREEN_ALPHA = 0.90f;
private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
@@ -77,6 +81,7 @@ public class DropZoneView extends FrameLayout {
private int mHighlightColor;
private ObjectAnimator mBackgroundAnimator;
+ private int mTargetBackgroundColor;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
@@ -146,6 +151,10 @@ public class DropZoneView extends FrameLayout {
/** Ignores the bottom margin provided by the insets. */
public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "setForceIgnoreBottomMargin: ignore=%b", ignoreBottomMargin);
+ }
mIgnoreBottomMargin = ignoreBottomMargin;
if (mMarginPercent > 0) {
mMarginView.invalidate();
@@ -154,8 +163,14 @@ public class DropZoneView extends FrameLayout {
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setBottomInset: inset=%f",
+ bottom);
+ }
mBottomInset = bottom;
- ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
+ final LayoutParams lp = (LayoutParams) mSplashScreenView.getLayoutParams();
+ lp.bottomMargin = (int) bottom;
+ mSplashScreenView.setLayoutParams(lp);
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
@@ -181,6 +196,9 @@ public class DropZoneView extends FrameLayout {
/** Animates between highlight and splashscreen depending on current state. */
public void animateSwitch() {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "animateSwitch");
+ }
mShowingHighlight = !mShowingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
@@ -190,6 +208,10 @@ public class DropZoneView extends FrameLayout {
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingHighlight: showing=%b",
+ showingHighlight);
+ }
mShowingHighlight = showingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
@@ -199,6 +221,10 @@ public class DropZoneView extends FrameLayout {
/** Animates the margins around the drop zone to show or hide. */
public void setShowingMargin(boolean visible) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingMargin: visible=%b",
+ visible);
+ }
if (mShowingMargin != visible) {
mShowingMargin = visible;
animateMarginToState();
@@ -212,6 +238,15 @@ public class DropZoneView extends FrameLayout {
}
private void animateBackground(int startColor, int endColor) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "animateBackground: start=%s end=%s",
+ Integer.toHexString(startColor), Integer.toHexString(endColor));
+ }
+ if (endColor == mTargetBackgroundColor) {
+ // Already at, or animating to, that background color
+ return;
+ }
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
}
@@ -223,6 +258,7 @@ public class DropZoneView extends FrameLayout {
mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
mBackgroundAnimator.start();
+ mTargetBackgroundColor = endColor;
}
private void animateSplashScreenIcon() {
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
index 31214eba8dd0..ffcfe6447e2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -25,7 +25,7 @@ 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.internal.protolog.ProtoLog
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.protolog.ShellProtoLogGroup
import java.util.function.Consumer
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 7d2aa275a684..4531967d6f93 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
@@ -25,11 +25,11 @@ import android.content.Context;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -150,6 +150,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);
}
+ } else if (repository.isClosingTask(taskInfo.taskId)
+ && repository.removeClosingTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Removing closing freeform task: #%d", taskInfo.taskId);
}
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
taskInfo.isVisible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 2626e7380163..d2ceb67030fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -27,7 +27,7 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 cd478e5bd567..333c75f92ffc 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
@@ -49,7 +49,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index ce98458c0575..93ede7a8b7aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.pip;
-import android.content.ComponentName;
import android.os.RemoteException;
import android.view.IPinnedTaskListener;
import android.view.WindowManagerGlobal;
@@ -70,12 +69,6 @@ public class PinnedStackListenerForwarder {
}
}
- private void onActivityHidden(ComponentName componentName) {
- for (PinnedTaskListener listener : mListeners) {
- listener.onActivityHidden(componentName);
- }
- }
-
@BinderThread
private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub {
@Override
@@ -91,13 +84,6 @@ public class PinnedStackListenerForwarder {
PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight);
});
}
-
- @Override
- public void onActivityHidden(ComponentName componentName) {
- mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onActivityHidden(componentName);
- });
- }
}
/**
@@ -108,7 +94,5 @@ public class PinnedStackListenerForwarder {
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
-
- public void onActivityHidden(ComponentName componentName) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index a749019046f8..b27c428f1693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,10 +16,12 @@
package com.android.wm.shell.pip;
+import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -69,9 +71,10 @@ public interface Pip {
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * @return {@link PipTransitionController} instance.
+ * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
+ * started / finished callbacks.
*/
- default PipTransitionController getPipTransitionController() {
- return null;
- }
+ default void registerPipTransitionCallback(
+ @NonNull PipTransitionController.PipTransitionCallback callback,
+ @NonNull Executor executor) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 0a3c15b6057f..a8346a9b3b48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -37,7 +38,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.pip.PipUtils;
@@ -625,6 +626,14 @@ public class PipAnimationController {
}
} else {
adjustedSourceRectHint.set(sourceRectHint);
+ if (isInPipDirection(direction)
+ && rotationDelta == ROTATION_0
+ && taskInfo.displayCutoutInsets != null) {
+ // TODO: this is to special case the issues on Foldable device
+ // with display cutout. This aligns with what's in SwipePipToHomeAnimator.
+ adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
}
final Rect sourceHintRectInsets = new Rect();
if (!adjustedSourceRectHint.isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 202f60dad842..3d1994cac534 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -137,7 +137,7 @@ public class PipSurfaceTransactionHelper {
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, left, top;
+ 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.
@@ -148,14 +148,17 @@ public class PipSurfaceTransactionHelper {
? (float) destinationBounds.width() / sourceBounds.width()
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
- left = destinationBounds.left - insets.left * scale;
- top = destinationBounds.top - insets.top * scale;
} else {
scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
(float) destinationBounds.height() / sourceBounds.height());
- // Work around the rounding error by fix the position at very beginning.
- left = scale == 1 ? 0 : destinationBounds.left - insets.left * scale;
- top = scale == 1 ? 0 : destinationBounds.top - insets.top * scale;
+ }
+ float left = destinationBounds.left - insets.left * scale;
+ float top = destinationBounds.top - insets.top * scale;
+ if (scale == 1) {
+ // Work around the 1 pixel off error by rounding the position down at very beginning.
+ // We noticed such error from flicker tests, not visually.
+ left = sourceBounds.left;
+ top = sourceBounds.top;
}
mTmpTransform.setScale(scale, scale);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
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 e4420d73886e..6e1e44b7ede0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,7 +63,6 @@ import android.content.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;
@@ -74,7 +73,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -128,8 +127,6 @@ 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;
@@ -426,7 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(
+ mPipTransitionCallback, mMainExecutor);
}
}
@@ -498,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ }
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
@@ -608,6 +608,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void exitPip(int animationDurationMs, boolean requestEnterSplit) {
if (!mPipTransitionState.isInPip()
|| mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
+ || mPipTransitionState.getInSwipePipToHomeTransition()
|| mToken == null) {
ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not allowed to exitPip in current state"
@@ -637,6 +638,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
+ // bail early if leash is null
+ if (mLeash == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "exitPip: leash is null");
+ return;
+ }
+
final Rect destinationBounds = new Rect(getExitDestinationBounds());
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
@@ -821,37 +829,6 @@ 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);
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 3cae72d89ecc..87692ac52908 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
@@ -24,6 +24,7 @@ import static android.util.RotationUtils.rotateBounds;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -62,7 +63,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -300,6 +301,10 @@ public class PipTransition extends PipTransitionController {
finishTransaction);
}
+ if (isCurrentPipActivityClosed(info)) {
+ mPipBoundsState.setLastPipComponentName(null /* componentName */);
+ }
+
return false;
}
@@ -322,6 +327,21 @@ public class PipTransition extends PipTransitionController {
return true;
}
+ private boolean isCurrentPipActivityClosed(TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ boolean isTaskChange = change.getTaskInfo() != null;
+ boolean hasComponentNameOfPip = change.getActivityComponent() != null
+ && change.getActivityComponent().equals(
+ mPipBoundsState.getLastPipComponentName());
+ if (!isTaskChange && change.getMode() == TRANSIT_CLOSE && hasComponentNameOfPip) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -1154,6 +1174,7 @@ public class PipTransition extends PipTransitionController {
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
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 6eefdcfc4d93..8d36db97c511 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
@@ -41,7 +41,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -53,8 +53,9 @@ import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Responsible supplying PiP Transitions.
@@ -66,7 +67,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+ private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -126,8 +127,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param duration the suggested duration for resize animation.
*/
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
// Default implementation does nothing.
}
@@ -181,16 +184,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(PipTransitionCallback callback) {
- mPipTransitionCallbacks.add(callback);
+ public void registerPipTransitionCallback(
+ @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
+ mPipTransitionCallbacks.put(callback, executor);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionStarted(direction, pipBounds);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -207,9 +212,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionFinished(direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionFinished(direction));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -226,9 +232,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionCanceled(direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionCanceled(direction));
}
}
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 0169e8c40f45..c7369a3cd347 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
@@ -32,7 +32,7 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsState;
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 8c4bf7620068..d1d82755d12c 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
@@ -59,7 +59,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
@@ -106,6 +106,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -367,15 +368,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
}
-
- @Override
- public void onActivityHidden(ComponentName componentName) {
- if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
- // The activity was removed, we don't want to restore to the reentry state
- // saved for this component anymore.
- mPipBoundsState.setLastPipComponentName(null);
- }
- }
}
/**
@@ -487,7 +479,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1229,8 +1221,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public PipTransitionController getPipTransitionController() {
- return mPipTransitionController;
+ public void registerPipTransitionCallback(
+ PipTransitionController.PipTransitionCallback callback,
+ Executor executor) {
+ mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
+ callback, executor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index f6cab485fa2a..d1978c30eecb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -28,7 +28,7 @@ import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 15342be0e8b7..c18964240f98 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
@@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
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 ef468434db6a..df3803d54d9d 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
@@ -34,10 +34,11 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -47,6 +48,7 @@ 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.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -171,7 +173,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -183,7 +187,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
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 d8ac8e948a97..9c4e723efc23 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
@@ -48,7 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 5d858fa9aa3f..cb82db630715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -23,7 +23,7 @@ import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 6b890c49b713..50d22ad00b11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -33,7 +33,7 @@ import android.app.RemoteAction;
import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
index 0221db836dda..eb7a10cc9dfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
@@ -28,7 +28,7 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 72c0cd71f198..188c35ff3562 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -33,7 +33,7 @@ import android.view.Gravity;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 8a215b4b2e25..1afb470c5e9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -26,7 +26,7 @@ import android.graphics.Rect;
import android.os.Handler;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 3d286461ef79..0ed5079b7fba 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
@@ -39,7 +39,7 @@ import android.view.Gravity;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -257,7 +257,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
index 977aad4a898a..327ceef00e6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -27,7 +27,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 6b5bdd2299e1..e74870d4d139 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
@@ -37,7 +37,7 @@ import android.window.SurfaceSyncGroup;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipMenuController;
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 adc03cf5c4d7..eabf1b0b3063 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
@@ -39,7 +39,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import java.util.Arrays;
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 4a767ef2a113..c7704f0b4eed 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
@@ -50,7 +50,7 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.widget.LinearLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
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 54e162bba2f3..ce5079227b61 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
@@ -33,7 +33,7 @@ import android.os.Bundle;
import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ImageUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipMediaController;
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 ca0d61f8fc9b..7a0e6694cb51 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
@@ -62,7 +62,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 5c561fed89c7..88f9e4c740e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -47,9 +47,17 @@ public class PipResizeAnimator extends ValueAnimator
@Nullable
private Runnable mAnimationEndCallback;
private RectEvaluator mRectEvaluator;
+
+ // Bounds relative to which scaling/cropping must be done.
private final Rect mBaseBounds = new Rect();
+
+ // Bounds to animate from.
private final Rect mStartBounds = new Rect();
+
+ // Target bounds.
private final Rect mEndBounds = new Rect();
+
+ // Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
private final float mDelta;
@@ -84,7 +92,6 @@ public class PipResizeAnimator extends ValueAnimator
addListener(this);
addUpdateListener(this);
setEvaluator(mRectEvaluator);
- // TODO: change this
setDuration(duration);
}
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
index 6e36a32ac931..9cfe1620a2ff 100644
--- 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
@@ -32,7 +32,7 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsState;
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 fc0d36d13b2e..06adad626e9f 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
@@ -36,7 +36,7 @@ import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -352,6 +352,7 @@ public class PipController implements ConfigurationChangeListener,
mPipBoundsAlgorithm.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipDisplayLayoutState.dump(pw, innerPrefix);
+ mPipTransitionState.dump(pw, innerPrefix);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
index b757b00f16dd..ffda56d89276 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
@@ -28,7 +28,7 @@ import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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
index 42b8e9f5a3ad..c54e4cd90f57 100644
--- 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
@@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 495cd0075494..ea02de9d9704 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
@@ -25,6 +26,7 @@ import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,7 +37,8 @@ import android.os.Bundle;
import android.os.Debug;
import android.view.SurfaceControl;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -45,6 +48,7 @@ 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.PipSnapAlgorithm;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
@@ -62,6 +66,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipMotionHelper";
private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
+ private static final String ANIMATING_BOUNDS_CHANGE = "animating_bounds_change";
private static final boolean DEBUG = false;
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
@@ -113,7 +118,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** SpringConfig to use for fling-then-spring animations. */
private final PhysicsAnimator.SpringConfig mSpringConfig =
- new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+ new PhysicsAnimator.SpringConfig(300f, DAMPING_RATIO_LOW_BOUNCY);
/** SpringConfig used for animating into the dismiss region, matches the one in
* {@link MagnetizedObject}. */
@@ -152,9 +157,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private boolean mDismissalPending = false;
/**
- * Set to true if bounds change transition has been scheduled from PipMotionHelper.
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper
+ * after animating is over.
*/
- private boolean mWaitingForBoundsChangeTransition = false;
+ private boolean mWaitingForFlingTransition = false;
+
+ /**
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper,
+ * and if the animation is supposed to run while transition is playing.
+ */
+ private boolean mWaitingToPlayBoundsChangeTransition = false;
/**
* Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
@@ -634,6 +646,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target.
if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // Update the earlier estimate on bounds we are animating towards, since physics
+ // animator is non-deterministic.
+ setAnimatingToBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
// do not schedule resize if PiP is dismissing, which may cause app re-open to
// mBounds instead of its normal bounds.
Bundle extra = new Bundle();
@@ -673,6 +688,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Directly resizes the PiP to the given {@param bounds}.
*/
private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ // Do not carry out any resizing if we are dragging or physics animator is running.
+ return;
+ }
+
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: resizeAndAnimatePipUnchecked: toBounds=%s"
@@ -682,10 +702,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// Intentionally resize here even if the current bounds match the destination bounds.
// This is so all the proper callbacks are performed.
-
- // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
- // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
- // setAnimatingToBounds(toBounds);
+ setAnimatingToBounds(toBounds);
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, duration);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
}
@Override
@@ -694,7 +715,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
@Nullable Bundle extra) {
switch (newState) {
case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
- if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+ mWaitingForFlingTransition = extra.getBoolean(FLING_BOUNDS_CHANGE);
+ mWaitingToPlayBoundsChangeTransition = extra.getBoolean(ANIMATING_BOUNDS_CHANGE);
+ if (!mWaitingForFlingTransition && !mWaitingToPlayBoundsChangeTransition) {
+ break;
+ }
if (mPipBoundsState.getBounds().equals(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
@@ -709,30 +734,30 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
break;
}
- // If touch is turned off and we are in a fling animation, schedule a transition.
- mWaitingForBoundsChangeTransition = true;
+ // Delay config until the end, if we are animating after scheduling the transition.
mPipScheduler.scheduleAnimateResizePip(
- mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().getAnimatingToBounds(),
+ mWaitingToPlayBoundsChangeTransition,
+ extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION));
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
- if (!mWaitingForBoundsChangeTransition) break;
-
- // If bounds change transition was scheduled from this class, handle leash updates.
- mWaitingForBoundsChangeTransition = false;
SurfaceControl.Transaction startTx = extra.getParcelable(
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishTx = extra.getParcelable(
+ PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
Rect destinationBounds = extra.getParcelable(
PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
- startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
- destinationBounds.left, destinationBounds.top);
- startTx.apply();
-
- // All motion operations have actually finished, so make bounds cache updates.
- settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
- cleanUpHighPerfSessionMaybe();
-
- // Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
+ if (mWaitingForFlingTransition) {
+ mWaitingForFlingTransition = false;
+ handleFlingTransition(startTx, finishTx, destinationBounds);
+ } else if (mWaitingToPlayBoundsChangeTransition) {
+ mWaitingToPlayBoundsChangeTransition = false;
+ startResizeAnimation(startTx, finishTx, destinationBounds, duration);
+ }
break;
case PipTransitionState.EXITING_PIP:
// We need to force finish any local animators if about to leave PiP, to avoid
@@ -740,9 +765,46 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
cancelPhysicsAnimation();
settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ break;
}
}
+ private void handleFlingTransition(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds) {
+ startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+ destinationBounds.left, destinationBounds.top);
+ startTx.apply();
+
+ // All motion operations have actually finished, so make bounds cache updates.
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ cleanUpHighPerfSessionMaybe();
+
+ // Signal that the transition is done - should update transition state by default.
+ mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ }
+
+ private void startResizeAnimation(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
+ SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ Preconditions.checkState(pipLeash != null,
+ "No leash cached by mPipTransitionState=" + mPipTransitionState);
+
+ startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
+ mPipBoundsState.getBounds().height());
+
+ PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
+ startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+ destinationBounds, duration, 0f /* angle */);
+ animator.setAnimationEndCallback(() -> {
+ mPipBoundsState.setBounds(destinationBounds);
+ // All motion operations have actually finished, so make bounds cache updates.
+ cleanUpHighPerfSessionMaybe();
+ // Signal that we are done with resize transition
+ mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ });
+ animator.start();
+ }
+
private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
if (!animatingAfter) {
// The physics animation ended, though we may not necessarily be done animating, such as
@@ -754,6 +816,26 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
+
+ // Check whether new bounds after fling imply we need to update stash state too.
+ stashEndActionIfNeeded();
+ }
+
+ private void stashEndActionIfNeeded() {
+ boolean isStashing = mPipBoundsState.getBounds().right > mPipBoundsState
+ .getDisplayBounds().width() || mPipBoundsState.getBounds().left < 0;
+ if (!isStashing) {
+ return;
+ }
+
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 33e80bd80988..5b0ca1837a1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.Nullable;
import android.content.Context;
@@ -535,7 +536,8 @@ public class PipResizeGestureHandler implements
mWaitingForBoundsChangeTransition = true;
// Schedule PiP resize transition, but delay any config updates until very end.
- mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
+ mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds,
+ true /* configAtEnd */, PINCH_RESIZE_SNAP_DURATION);
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
if (!mWaitingForBoundsChangeTransition) break;
@@ -550,12 +552,15 @@ public class PipResizeGestureHandler implements
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishTx = extra.getParcelable(
PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
mPipBoundsState.getBounds().height());
PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
- mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle);
+ mLastResizeBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
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 9c1e321a1273..ac670cf3f828 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
@@ -33,7 +33,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
@@ -162,6 +162,18 @@ public class PipScheduler {
* @param configAtEnd true if we are delaying config updates until the transition ends.
*/
public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) {
+ scheduleAnimateResizePip(toBounds, configAtEnd,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+ }
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ *
+ * @param configAtEnd true if we are delaying config updates until the transition ends.
+ * @param duration the suggested duration to run the animation; the component responsible
+ * for running the animator will get this as an extra.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
@@ -170,7 +182,7 @@ public class PipScheduler {
if (configAtEnd) {
wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
}
- mPipTransitionController.startResizeTransition(wct);
+ mPipTransitionController.startResizeTransition(wct, duration);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 56a465a4889a..53b80e8b7542 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -51,7 +51,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -106,9 +106,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
private float mStashVelocityThreshold;
- // The reference inset bounds, used to determine the dismiss fraction
- private final Rect mInsetBounds = new Rect();
-
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds
private int mDeferResizeToNormalBoundsUntilRotation = -1;
@@ -206,17 +203,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
- if (mPipBoundsState.isStashed()) {
- animateToUnStashedState();
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
- } else {
- mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
- mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
- willResizeMenu(),
- shouldShowResizeHandle());
- }
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
},
menuController::hideMenu,
mainExecutor);
@@ -438,7 +428,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
mDisplayRotation = displayRotation;
- mInsetBounds.set(insetBounds);
updateMovementBounds();
mMovementBoundsExtraOffsets = extraOffset;
@@ -713,15 +702,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
}
}
- private void animateToMaximizedState(Runnable callback) {
- Rect maxMovementBounds = new Rect();
+ private void animateToMaximizedState() {
Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
mPipBoundsState.getMaxSize().y);
- mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
- mIsImeShowing ? mImeHeight : 0);
+
mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
- mPipBoundsState.getMovementBounds(), maxMovementBounds,
- callback);
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(maxBounds), null /* callback */);
}
private void animateToNormalSize(Runnable callback) {
@@ -729,22 +716,20 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
- final Rect normalBounds = mPipBoundsState.getNormalBounds();
- final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
- minMenuSize);
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(destBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
- mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
+ final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+ final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
+ normalBounds, minMenuSize);
+
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(adjustedNormalBounds), callback /* callback */);
}
private void animateToUnexpandedState(Rect restoreBounds) {
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
- restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ getMovementBounds(restoreBounds),
+ getMovementBounds(mPipBoundsState.getBounds()), false /* immediate */);
mSavedSnapFraction = -1f;
}
@@ -752,10 +737,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
final Rect pipBounds = mPipBoundsState.getBounds();
final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
- unStashedBounds.left = onLeftEdge ? mInsetBounds.left
- : mInsetBounds.right - pipBounds.width();
- unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
- : mInsetBounds.right;
+
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ unStashedBounds.left = onLeftEdge ? insetBounds.left
+ : insetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? insetBounds.left + pipBounds.width()
+ : insetBounds.right;
mMotionHelper.animateToUnStashedBounds(unStashedBounds);
}
@@ -903,8 +891,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// Reset the touch state on up before the fling settles
mTouchState.reset();
if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
- // mMotionHelper.stashToEdge(vel.x, vel.y,
- // this::stashEndAction /* endAction */);
+ mMotionHelper.stashToEdge(vel.x, vel.y, null /* endAction */);
} else {
if (mPipBoundsState.isStashed()) {
// Reset stashed state if previously stashed
@@ -919,10 +906,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
&& mMenuState != MENU_STATE_FULL) {
// If using pinch to zoom, double-tap functions as resizing between max/min size
if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- final boolean toExpand = mPipBoundsState.getBounds().width()
- < mPipBoundsState.getMaxSize().x
- && mPipBoundsState.getBounds().height()
- < mPipBoundsState.getMaxSize().y;
if (mMenuController.isMenuVisible()) {
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
@@ -934,7 +917,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// actually toggle to the size chosen
if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
- animateToMaximizedState(null);
+ animateToMaximizedState();
} else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToNormalSize(null);
@@ -1038,14 +1021,18 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
* resized.
*/
private void updateMovementBounds() {
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
- mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
}
private Rect getMovementBounds(Rect curBounds) {
Rect movementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
movementBounds, mIsImeShowing ? mImeHeight : 0);
return movementBounds;
}
@@ -1091,6 +1078,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
case PipTransitionState.ENTERED_PIP:
onActivityPinned();
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
case PipTransitionState.EXITED_PIP:
mTouchState.setAllowInputEvents(false);
@@ -1101,6 +1089,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
break;
case PipTransitionState.CHANGED_PIP_BOUNDS:
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
index d093f1e5ccc1..bb8d4ee9c80f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
@@ -23,7 +23,7 @@ import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 57dc5f92b2b6..683d30d59b33 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
@@ -71,6 +71,9 @@ public class PipTransition extends PipTransitionController implements
static final String PIP_START_TX = "pip_start_tx";
static final String PIP_FINISH_TX = "pip_finish_tx";
static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
+ static final String ANIMATING_BOUNDS_CHANGE_DURATION =
+ "animating_bounds_change_duration";
+ static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -87,7 +90,7 @@ public class PipTransition extends PipTransitionController implements
private final PipTransitionState mPipTransitionState;
//
- // Transition tokens
+ // Transition caches
//
@Nullable
@@ -96,6 +99,8 @@ public class PipTransition extends PipTransitionController implements
private IBinder mExitViaExpandTransition;
@Nullable
private IBinder mResizeTransition;
+ private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+
//
// Internal state and relevant cached info
@@ -152,11 +157,12 @@ public class PipTransition extends PipTransitionController implements
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mBoundsChangeDuration = duration;
}
@Nullable
@@ -272,6 +278,10 @@ public class PipTransition extends PipTransitionController implements
extra.putParcelable(PIP_START_TX, startTransaction);
extra.putParcelable(PIP_FINISH_TX, finishTransaction);
extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
+ if (mBoundsChangeDuration > BOUNDS_CHANGE_JUMPCUT_DURATION) {
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, mBoundsChangeDuration);
+ mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+ }
mFinishCallback = finishCallback;
mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 9d599caf13dd..29272be6e9bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -62,6 +63,8 @@ import java.util.List;
* and throw an <code>IllegalStateException</code> otherwise.</p>
*/
public class PipTransitionState {
+ private static final String TAG = PipTransitionState.class.getSimpleName();
+
public static final int UNDEFINED = 0;
// State for Launcher animating the swipe PiP to home animation.
@@ -190,8 +193,9 @@ public class PipTransitionState {
"No extra bundle for " + stateToString(state) + " state.");
}
if (mState != state) {
- dispatchPipTransitionStateChanged(mState, state, extra);
+ final int prevState = mState;
mState = state;
+ dispatchPipTransitionStateChanged(prevState, mState, extra);
}
}
@@ -319,4 +323,11 @@ public class PipTransitionState {
return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
stateToString(mState), mInSwipePipToHomeTransition);
}
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mState=" + stateToString(mState));
+ }
}
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 03c8cf8cc795..9539a456502f 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
@@ -37,13 +37,14 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.IRecentsAnimationRunner;
+import android.window.WindowContainerToken;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -52,9 +53,9 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -65,9 +66,11 @@ import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -394,6 +397,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+ Set<Integer> minimizedFreeformTasks = new HashSet<>();
int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
@@ -409,15 +413,14 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (DesktopModeStatus.canEnterDesktopMode(mContext)
&& mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
- if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
- // Minimized freeform tasks should not be shown at all.
- continue;
- }
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
}
freeformTasks.add(taskInfo);
+ if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ minimizedFreeformTasks.add(taskInfo.taskId);
+ }
continue;
}
@@ -435,8 +438,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
- recentTasks.add(mostRecentFreeformTaskIndex, GroupedRecentTaskInfo.forFreeformTasks(
- freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
+ recentTasks.add(mostRecentFreeformTaskIndex,
+ GroupedRecentTaskInfo.forFreeformTasks(
+ freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]),
+ minimizedFreeformTasks));
}
return recentTasks;
@@ -453,11 +458,31 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
- * Find the background task that match the given component.
+ * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
+ * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask(
+ @Nullable WindowContainerToken ignoreTaskToken) {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2,
+ false /* filterOnlyVisibleRecents */);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
+ return task;
+ }
+ return null;
+ }
+
+ /**
+ * Find the background task that match the given component. Ignores tasks match
+ * {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
- int userId) {
+ int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
@@ -469,6 +494,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (task.isVisible) {
continue;
}
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
return task;
}
@@ -636,7 +664,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
@Override
public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) {
final ActivityManager.RunningTaskInfo[][] tasks =
- new ActivityManager.RunningTaskInfo[][] {null};
+ new ActivityManager.RunningTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRunningTasks",
(controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum)
.toArray(new ActivityManager.RunningTaskInfo[0]),
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 3a266d9bb3ef..234b4d0f86db 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
@@ -63,7 +63,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 7c5f10a5bcca..8ee72b499e5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -76,21 +76,40 @@ class TaskStackTransitionObserver(
continue
}
+ // Filter out changes that we care about
if (change.mode == WindowManager.TRANSIT_OPEN) {
change.taskInfo?.let { taskInfoList.add(it) }
transitionTypeList.add(change.mode)
}
}
- transitionToTransitionChanges.put(
- transition,
- TransitionChanges(taskInfoList, transitionTypeList)
- )
+ // Only add the transition to map if it has a change we care about
+ if (taskInfoList.isNotEmpty()) {
+ transitionToTransitionChanges.put(
+ transition,
+ TransitionChanges(taskInfoList, transitionTypeList)
+ )
+ }
}
}
override fun onTransitionStarting(transition: IBinder) {}
- override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
+ val mergedTransitionChanges =
+ transitionToTransitionChanges.get(merged)
+ ?:
+ // We are adding changes of the merged transition to changes of the playing
+ // transition so if there is no changes nothing to do.
+ return
+
+ transitionToTransitionChanges.remove(merged)
+ val playingTransitionChanges = transitionToTransitionChanges.get(playing)
+ if (playingTransitionChanges != null) {
+ playingTransitionChanges.merge(mergedTransitionChanges)
+ } else {
+ transitionToTransitionChanges.put(playing, mergedTransitionChanges)
+ }
+ }
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
val taskInfoList =
@@ -138,6 +157,11 @@ class TaskStackTransitionObserver(
private data class TransitionChanges(
val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(),
- val transitionTypeList: MutableList<Int> = ArrayList()
- )
+ val transitionTypeList: MutableList<Int> = ArrayList(),
+ ) {
+ fun merge(transitionChanges: TransitionChanges) {
+ taskInfoList.addAll(transitionChanges.taskInfoList)
+ transitionTypeList.addAll(transitionChanges.transitionTypeList)
+ }
+ }
}
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 64e26dbd70be..1cbb8bbe5f75 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
@@ -23,7 +23,7 @@ import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
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 f5fbae55960a..27fd309c09b0 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
@@ -24,7 +24,7 @@ import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
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 dd219d32bbaa..e659151fee7f 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
@@ -64,6 +64,7 @@ import android.view.SurfaceSession;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -73,7 +74,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -526,7 +527,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
}
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Starts an existing task into split.
+ * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead
+ * @param hideTaskToken is not supported.
+ */
+ public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Legacy startTask does not support hide task token");
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
@@ -584,8 +593,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
- user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
+ user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) {
if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
@@ -676,10 +685,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
* See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
- public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+ public void startIntentWithInstanceId(PendingIntent intent, int userId,
+ @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
+ @NonNull InstanceId instanceId) {
mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, userId, fillInIntent, position, options);
+ startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
}
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@@ -825,9 +835,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
+ /**
+ * Starts the given intent into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -838,23 +854,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
final String packageName1 = SplitScreenUtils.getPackageName(intent);
- final String packageName2 = getPackageName(reverseSplitPosition(position));
- final int userId2 = getUserId(reverseSplitPosition(position));
+ final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
+ final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
final ComponentName component = intent.getIntent().getComponent();
// To prevent accumulating large number of instances in the background, reuse task
// in the background. If we don't explicitly reuse, new may be created even if the app
// isn't multi-instance because WM won't automatically remove/reuse the previous instance
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
+ hideTaskToken))
.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);
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
} else {
- startTask(taskInfo.taskId, position, options);
+ startTask(taskInfo.taskId, position, options, hideTaskToken);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
@@ -879,19 +896,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
- mStageCoordinator.startIntent(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken);
}
- /** Retrieve package name of a specific split position if split screen is activated, otherwise
- * returns the package name of the top running task. */
+ /**
+ * Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task.
+ * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls
+ */
@Nullable
- private String getPackageName(@SplitPosition int position) {
+ private String getPackageName(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return null;
@@ -901,15 +922,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
- /** Retrieve user id of a specific split position if split screen is activated, otherwise
- * returns the user id of the top running task. */
- private int getUserId(@SplitPosition int position) {
+ /**
+ * Retrieve user id of a specific split position if split screen is activated, otherwise
+ * returns the user id of the top running task.
+ * TODO: Merge this with getPackageName() so we don't make multiple binder calls
+ */
+ private int getUserId(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return -1;
@@ -1290,7 +1315,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> controller.startTask(taskId, position, options));
+ (controller) -> controller.startTask(taskId, position, options,
+ null /* hideTaskToken */));
}
@Override
@@ -1402,8 +1428,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> controller.startIntent(intent, userId, fillInIntent, position,
- options, instanceId));
+ (controller) -> controller.startIntentWithInstanceId(intent, userId,
+ fillInIntent, position, options, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 0541a0287179..b3dab8527617 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
@@ -46,7 +46,7 @@ import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 b6a18e537600..41042344fd3a 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
@@ -119,7 +119,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
@@ -592,12 +592,21 @@ 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) {
+ /**
+ * Use this method to launch an existing Task via a taskId.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
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 */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
@@ -623,9 +632,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
extraTransitType, !mIsDropEntering);
}
- /** Launches an activity into split. */
+ /**
+ * Launches an activity into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
+ @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
@@ -636,6 +649,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
@@ -2649,7 +2666,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
- if (isSplitActive()) {
+ if (isSplitScreenVisible()) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
request.getDebugId());
// Check if the display is rotating.
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 0f3d6cade95a..1076eca60369 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
@@ -45,7 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
index 1d8a8d506c5c..e0f63940663a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
@@ -36,7 +36,7 @@ import android.view.LayoutInflater;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.split.SplitScreenConstants;
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 6325c686a682..ea8c0eb30061 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,8 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
+ mFromYDelta = firstWindowFrame.top
+ - 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 2b12a22f907d..90eb22f369df 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
@@ -74,7 +74,7 @@ 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.internal.protolog.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.TransactionPool;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index e552e6cdacf3..08211ab5df9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -53,7 +53,7 @@ import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 3353c7bd81c2..97a695f34cf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -43,7 +43,7 @@ import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
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 66b3553bea09..6e084d6e05a4 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
@@ -21,8 +21,6 @@ import static android.graphics.Color.WHITE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
-
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,7 +28,6 @@ import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
@@ -51,7 +48,7 @@ import android.window.SnapshotDrawerUtils;
import android.window.StartingWindowInfo;
import android.window.TaskSnapshot;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.view.BaseIWindow;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -139,16 +136,10 @@ public class TaskSnapshotWindow {
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- if (windowSessionRelayoutInfo()) {
- final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames,
- tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
- outRelayoutResult);
- } else {
- session.relayoutLegacy(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
- tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, new Bundle());
- }
+ final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames,
+ tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
+ outRelayoutResult);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index 72fc8686f648..2036d9c13f0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -35,7 +35,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index 2e6ddc363906..aa9f15c37531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -18,7 +18,7 @@ package com.android.wm.shell.sysui;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import java.io.PrintWriter;
import java.util.Arrays;
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 5ced1fb41a41..0202b6cf3eab 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
@@ -40,7 +40,7 @@ import android.view.SurfaceControlRegistry;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 2e2f569a52b8..dd4595a70211 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -25,8 +25,9 @@ import android.view.SurfaceControl;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -75,6 +76,7 @@ public class ShellInit {
*/
@VisibleForTesting
public void init() {
+ ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
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 a126cbe41b00..9750d3ec99f5 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
@@ -535,7 +535,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */,
+ 0 /* flags */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
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 b03daaafd70c..35427b93acea 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
@@ -94,6 +94,11 @@ public class CounterRotatorHelper {
return rotatedBounds;
}
+ /** Returns true if the change is put on a surface in previous rotation. */
+ public boolean isRotated(@NonNull TransitionInfo.Change change) {
+ return mLastRotationDelta != 0 && mRotatorMap.containsKey(change.getParent());
+ }
+
/**
* Removes the counter rotation surface in the finish transaction. No need to reparent the
* children as the finish transaction should have already taken care of that.
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 8ee1efa90a30..4f4b8097cfac 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
@@ -39,7 +39,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index c33fb80fdefc..c8921d256d7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -28,7 +28,7 @@ import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
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 018c9044e2f7..f33a57399c22 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -53,6 +54,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
@@ -102,7 +104,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -517,7 +519,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y);
}
- if (change.getActivityComponent() != null && !isActivityLevel) {
+ if (change.getActivityComponent() != null && !isActivityLevel
+ && !mRotator.isRotated(change)) {
// At this point, this is an independent activity change in a non-activity
// transition. This means that an activity transition got erroneously combined
// with another ongoing transition. This then means that the animation root may
@@ -943,12 +946,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
private static int getWallpaperTransitType(TransitionInfo info) {
+ boolean hasWallpaper = false;
boolean hasOpenWallpaper = false;
boolean hasCloseWallpaper = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
+ if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0
+ || (change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ hasWallpaper = true;
if (TransitionUtil.isOpeningType(change.getMode())) {
hasOpenWallpaper = true;
} else if (TransitionUtil.isClosingType(change.getMode())) {
@@ -964,6 +970,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return WALLPAPER_TRANSITION_OPEN;
} else if (hasCloseWallpaper) {
return WALLPAPER_TRANSITION_CLOSE;
+ } else if (hasWallpaper) {
+ return WALLPAPER_TRANSITION_CHANGE;
} else {
return WALLPAPER_TRANSITION_NONE;
}
@@ -978,7 +986,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int animType = options.getType();
return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
|| animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
- || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
+ || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS
+ || animType == ANIM_FROM_STYLE;
}
private static void applyTransformation(long time, SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 89b0e25b306b..978b8da2eb6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -28,7 +28,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IWindowContainerTransactionCallback;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
/**
* Utilities and interfaces for transition-like usage on top of the legacy app-transition and
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
index e8b01b5880fb..8cc7f212af25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -35,7 +35,7 @@ import android.annotation.Nullable;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -184,7 +184,8 @@ public class MixedTransitionHelper {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange || !isOpeningMode(change.getMode())) {
+ if (change == pipChange || !isOpeningMode(change.getMode()) ||
+ change.getTaskInfo() == null) {
// Ignore the change/task that's going into Pip or not opening
continue;
}
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 69c41675e989..c5dc668582bc 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
@@ -30,7 +30,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowAnimationState;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 9fc6702562bb..391c5fe3473c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -30,7 +30,7 @@ import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
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 d6860464d055..6013a1ea1d9d 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 android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
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 2047b5a88604..a5f071af6973 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
@@ -54,7 +54,7 @@ import android.window.TransitionInfo;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
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 f257e207673d..5b4208a10907 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
@@ -28,12 +28,17 @@ import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
+import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -72,7 +77,8 @@ import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -512,12 +518,17 @@ public class Transitions implements RemoteCallable<Transitions>,
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
+ if (change.hasFlags(FLAGS_IS_NON_APP_WINDOW & ~FLAG_IS_WALLPAPER)) {
// Currently system windows are controlled by WindowState, so don't change their
// surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
- // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
- // app), because there may not be a transition associated with their visibility
- // changes, and currently they don't need transition animation.
+ // This includes IME (associated with app), because there may not be a transition
+ // associated with their visibility changes, and currently they don't need a
+ // transition animation.
+ continue;
+ }
+ if (change.hasFlags(FLAG_IS_WALLPAPER) && !ensureWallpaperInTransitions()) {
+ // Wallpaper is always z-ordered at bottom, and historically is not animated by
+ // transition handlers.
continue;
}
final SurfaceControl leash = change.getLeash();
@@ -570,6 +581,14 @@ public class Transitions implements RemoteCallable<Transitions>,
final boolean isOpening = isOpeningType(transitType);
final boolean isClosing = isClosingType(transitType);
final int mode = change.getMode();
+ // Ensure wallpapers stay in the back
+ if (change.hasFlags(FLAG_IS_WALLPAPER) && Flags.ensureWallpaperInTransitions()) {
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ return -zSplitLine + numChanges - i;
+ } else {
+ return -zSplitLine - i;
+ }
+ }
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
@@ -788,14 +807,16 @@ public class Transitions implements RemoteCallable<Transitions>,
final int changeSize = info.getChanges().size();
boolean taskChange = false;
boolean transferStartingWindow = false;
- int noAnimationBehindStartingWindow = 0;
+ int animBehindStartingWindow = 0;
boolean allOccluded = changeSize > 0;
for (int i = changeSize - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
taskChange |= change.getTaskInfo() != null;
transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
- if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
- noAnimationBehindStartingWindow++;
+ if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)
+ || change.hasAllFlags(
+ FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ animBehindStartingWindow++;
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
@@ -813,11 +834,11 @@ public class Transitions implements RemoteCallable<Transitions>,
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
// task change. Since this is an activity->activity situation, we can detect it by selecting
- // transitions with only 2 changes where
- // 1. neither are tasks, and
+ // transitions with changes where
+ // 1. none are tasks, and
// 2. one is a starting-window recipient, or all change is behind starting window.
- if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
- && changeSize == 2
+ if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize)
+ && changeSize >= 1
// B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -1186,7 +1207,7 @@ public class Transitions implements RemoteCallable<Transitions>,
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
- + "type=%d wct=%s handler=%s", type, wct, handler);
+ + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
final ActiveTransition active =
new ActiveTransition(mOrganizer.startNewTransition(type, wct));
active.mHandler = handler;
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 7c2ba455c0c9..88bfebf9331e 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
@@ -33,7 +33,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
index c045cebdf4e0..a2d2b9aff597 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Simple container for recent tasks. May contain either a single or pair of tasks.
@@ -50,6 +51,9 @@ public class GroupedRecentTaskInfo implements Parcelable {
private final SplitBounds mSplitBounds;
@GroupType
private final int mType;
+ // TODO(b/348332802): move isMinimized inside each Task object instead once we have a
+ // replacement for RecentTaskInfo
+ private final int[] mMinimizedTaskIds;
/**
* Create new for a single task
@@ -57,7 +61,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
public static GroupedRecentTaskInfo forSingleTask(
@NonNull ActivityManager.RecentTaskInfo task) {
return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null,
- TYPE_SINGLE);
+ TYPE_SINGLE, null /* minimizedFreeformTasks */);
}
/**
@@ -66,28 +70,51 @@ public class GroupedRecentTaskInfo implements Parcelable {
public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1,
@NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) {
return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2},
- splitBounds, TYPE_SPLIT);
+ splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */);
}
/**
* Create new for a group of freeform tasks
*/
public static GroupedRecentTaskInfo forFreeformTasks(
- @NonNull ActivityManager.RecentTaskInfo... tasks) {
- return new GroupedRecentTaskInfo(tasks, null, TYPE_FREEFORM);
+ @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ @NonNull Set<Integer> minimizedFreeformTasks) {
+ return new GroupedRecentTaskInfo(
+ tasks,
+ null /* splitBounds */,
+ TYPE_FREEFORM,
+ minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
}
- private GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo[] tasks,
- @Nullable SplitBounds splitBounds, @GroupType int type) {
+ private GroupedRecentTaskInfo(
+ @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ @Nullable SplitBounds splitBounds,
+ @GroupType int type,
+ @Nullable int[] minimizedFreeformTaskIds) {
mTasks = tasks;
mSplitBounds = splitBounds;
mType = type;
+ mMinimizedTaskIds = minimizedFreeformTaskIds;
+ ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
+ }
+
+ private static void ensureAllMinimizedIdsPresent(
+ @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ @Nullable int[] minimizedFreeformTaskIds) {
+ if (minimizedFreeformTaskIds == null) {
+ return;
+ }
+ if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
+ taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) {
+ throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
+ }
}
GroupedRecentTaskInfo(Parcel parcel) {
mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR);
mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
mType = parcel.readInt();
+ mMinimizedTaskIds = parcel.createIntArray();
}
/**
@@ -135,6 +162,10 @@ public class GroupedRecentTaskInfo implements Parcelable {
return mType;
}
+ public int[] getMinimizedTaskIds() {
+ return mMinimizedTaskIds;
+ }
+
@Override
public String toString() {
StringBuilder taskString = new StringBuilder();
@@ -161,6 +192,8 @@ public class GroupedRecentTaskInfo implements Parcelable {
taskString.append("TYPE_FREEFORM");
break;
}
+ taskString.append(", Minimized Task IDs: ");
+ taskString.append(Arrays.toString(mMinimizedTaskIds));
return taskString.toString();
}
@@ -181,6 +214,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
parcel.writeTypedArray(mTasks, flags);
parcel.writeTypedObject(mSplitBounds, flags);
parcel.writeInt(mType);
+ parcel.writeIntArray(mMinimizedTaskIds);
}
@Override
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
deleted file mode 100644
index 564e716c7378..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ /dev/null
@@ -1,74 +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.util
-
-import android.util.Log
-import com.android.internal.protolog.common.IProtoLogGroup
-import com.android.internal.protolog.common.ProtoLog
-
-/**
- * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
- * logging from Kotlin classes as ProtoLog does not have support for Kotlin.
- *
- * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
- */
-// TODO(b/168581922): remove once ProtoLog adds support for Kotlin
-class KtProtoLog {
- companion object {
- /** @see [com.android.internal.protolog.common.ProtoLog.d] */
- fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- 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 (group.isLogToLogcat) {
- 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 (group.isLogToLogcat) {
- 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 (group.isLogToLogcat) {
- 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 (group.isLogToLogcat) {
- 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 (group.isLogToLogcat) {
- Log.wtf(group.tag, String.format(messageString, *args))
- }
- }
- }
-}
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 21b6db29143a..faf6a627febe 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
@@ -22,18 +22,18 @@ 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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
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_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
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.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
+import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -42,12 +42,17 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -73,7 +78,9 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -90,7 +97,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -101,9 +108,9 @@ 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 com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import java.io.PrintWriter;
-import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
@@ -129,6 +136,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private final DesktopTasksController mDesktopTasksController;
private final InputManager mInputManager;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -184,7 +192,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ InteractionJankMonitor interactionJankMonitor
) {
this(
context,
@@ -205,7 +214,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
rootTaskDisplayAreaOrganizer,
- new SparseArray<>());
+ new SparseArray<>(),
+ interactionJankMonitor);
}
@VisibleForTesting
@@ -228,7 +238,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
+ InteractionJankMonitor interactionJankMonitor) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -251,6 +262,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId = windowDecorByTaskId;
mSysUIPackageName = mContext.getResources().getString(
com.android.internal.R.string.config_systemUi);
+ mInteractionJankMonitor = interactionJankMonitor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -381,10 +393,52 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
+ private void onMaximizeOrRestore(int taskId, String tag) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ mInteractionJankMonitor.begin(
+ decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ }
+
+ private void onSnapResize(int taskId, boolean left) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
+ left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ }
+
+ private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ openInBrowser(uri);
+ decor.closeHandleMenu();
+ decor.closeMaximizeMenu();
+ }
+
+ private void openInBrowser(Uri uri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .setComponent(getDefaultBrowser())
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private ComponentName getDefaultBrowser() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+ final ResolveInfo info = mContext.getPackageManager()
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return info.getComponentInfo().getComponentName();
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
- private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -403,7 +457,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
private int mDragPointerId = -1;
- private final Runnable mCloseMaximizeWindowRunnable;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
@@ -414,11 +467,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragDetector = new DragDetector(this);
mGestureDetector = new GestureDetector(mContext, this);
mDisplayId = taskInfo.displayId;
- mCloseMaximizeWindowRunnable = () -> {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- if (decoration == null) return;
- decoration.closeMaximizeMenu();
- };
}
@Override
@@ -435,7 +483,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mDesktopTasksController.onDesktopWindowClose(wct, mTaskId);
+ mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId);
mTaskOperations.closeTask(mTaskToken, wct);
}
} else if (id == R.id.back_button) {
@@ -467,28 +515,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
+ } else if (id == R.id.open_in_browser_button) {
+ // TODO(b/346441962): let the decoration handle the click gesture and only call back
+ // to the ViewModel via #setOpenInBrowserClickListener
+ decoration.onOpenInBrowserClick();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
- mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
- } else if (id == R.id.maximize_menu_maximize_button) {
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
- } else if (id == R.id.maximize_menu_snap_left_button) {
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- 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.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
+ // TODO(b/346441962): move click detection logic into the decor's
+ // {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
+ // back to the decoration using
+ // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
+ // should shared with the maximize menu's maximize/restore actions.
+ onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
}
}
@@ -570,40 +609,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
+ /**
+ * TODO(b/346441962): move this hover detection logic into the decor's
+ * {@link AppHeaderViewHolder}.
+ */
@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);
- if (id != R.id.maximize_window) {
- decoration.onMaximizeMenuHoverEnter(id, ev);
- }
+ if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+ if (!decoration.isMaximizeMenuActive()) {
+ decoration.onMaximizeButtonHoverEnter();
}
return true;
- } else if (ev.getAction() == ACTION_HOVER_MOVE
- && MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- decoration.onMaximizeMenuHoverMove(id, ev);
- mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
- } else if (ev.getAction() == ACTION_HOVER_EXIT) {
- if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
- decoration.onMaximizeWindowHoverExit();
- } else if (id == R.id.maximize_window
- || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- // 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);
- if (id != R.id.maximize_window) {
- decoration.onMaximizeMenuHoverExit(id, ev);
- }
+ }
+ if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) {
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+ decoration.onMaximizeHoverStateChanged();
+ if (!decoration.isMaximizeMenuActive()) {
+ decoration.onMaximizeButtonHoverExit();
}
return true;
}
@@ -711,8 +736,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+ onMaximizeOrRestore(mTaskId, "double_tap");
return true;
}
}
@@ -1039,12 +1063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& taskInfo.isFocused) {
return false;
}
- // TODO(b/347289970): Consider replacing with API
if (Flags.enableDesktopWindowingModalsPolicy()
- && isSingleTopActivityTranslucent(taskInfo)) {
- return false;
- }
- if (isSystemUIApplication(taskInfo)) {
+ && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
return DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1087,14 +1107,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions);
+ mDragStartListener, mTransitions, mInteractionJankMonitor);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
-
+ windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
+ windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
+ onSnapResize(taskId, true /* isLeft */);
+ });
+ windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
+ onSnapResize(taskId, false /* isLeft */);
+ });
+ windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
@@ -1117,14 +1144,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& mSplitScreenController.isTaskInSplitScreen(taskId);
}
- // TODO(b/347289970): Consider replacing with API
- private boolean isSystemUIApplication(RunningTaskInfo taskInfo) {
- if (taskInfo.baseActivity != null) {
- return (Objects.equals(taskInfo.baseActivity.getPackageName(), mSysUIPackageName));
- }
- return false;
- }
-
private void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + "DesktopModeWindowDecorViewModel");
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 4d597cac889e..5ffd883a7ceb 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
@@ -31,6 +31,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResiz
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -43,6 +44,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
@@ -67,8 +69,9 @@ 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;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -86,6 +89,10 @@ import java.util.function.Supplier;
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+ private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
+
+ @VisibleForTesting
+ static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
private final Handler mHandler;
private final Choreographer mChoreographer;
@@ -96,6 +103,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
+ private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
+ private OnTaskActionClickListener mOnLeftSnapClickListener;
+ private OnTaskActionClickListener mOnRightSnapClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -116,10 +126,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Bitmap mResizeVeilBitmap;
private CharSequence mAppName;
+ private CapturedLink mCapturedLink;
+ private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final MaximizeMenuFactory mMaximizeMenuFactory;
+
+ // Hover state for the maximize menu and button. The menu will remain open as long as either of
+ // these is true. See {@link #onMaximizeHoverStateChanged()}.
+ private boolean mIsAppHeaderMaximizeButtonHovered = false;
+ private boolean mIsMaximizeMenuHovered = false;
+ // Used to schedule the closing of the maximize menu when neither of the button or menu are
+ // being hovered. There's a small delay after stopping the hover, to allow a quick reentry
+ // to cancel the close.
+ private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
+ private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
DesktopModeWindowDecoration(
Context context,
@@ -135,7 +158,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {});
+ new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -152,7 +175,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
- SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ MaximizeMenuFactory maximizeMenuFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -161,6 +185,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mMaximizeMenuFactory = maximizeMenuFactory;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks' maximize/restore action is
+ * triggered.
+ * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
+ * having the ViewModel deal with parsing motion events.
+ */
+ void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
+ mOnMaximizeOrRestoreClickListener = listener;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks snap-left action is triggered.
+ */
+ void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
+ mOnLeftSnapClickListener = listener;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks' snap-right action is triggered.
+ */
+ void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
+ mOnRightSnapClickListener = listener;
}
void setCaptionListeners(
@@ -187,6 +236,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
+ void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ mOpenInBrowserClickListener = listener;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -278,6 +331,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
+
+ if (Flags.enableDesktopWindowingAppToWeb()) {
+ setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
+ }
+
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
@@ -322,6 +380,28 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
+ private void setCapturedLink(Uri capturedLink, long timeStamp) {
+ if (capturedLink == null
+ || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
+ return;
+ }
+ mCapturedLink = new CapturedLink(capturedLink, timeStamp);
+ mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
+ }
+
+ private void onCapturedLinkExpired() {
+ mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
+ if (mCapturedLink != null) {
+ mCapturedLink.setExpired();
+ }
+ }
+
+ void onOpenInBrowserClick() {
+ if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
+ mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ onCapturedLinkExpired();
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -556,12 +636,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (mAppIconBitmap != null && mAppName != null) {
return;
}
- final ActivityInfo activityInfo = mTaskInfo.topActivityInfo;
- if (activityInfo == null) {
- Log.e(TAG, "Top activity info not found in task");
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ if (baseActivity == null) {
+ Log.e(TAG, "Base activity component not found in task");
return;
}
- PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -575,6 +656,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Base activity's component name cannot be found on the system");
} finally {
Trace.endSection();
}
@@ -714,11 +797,41 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* Create and display maximize menu window
*/
void createMaximizeMenu() {
- mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
- mDisplayController, mTaskInfo, mOnCaptionButtonClickListener,
- mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext,
+ mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
+ mDisplayController, mTaskInfo, mContext,
calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
- mMaximizeMenu.show();
+ mMaximizeMenu.show(
+ mOnMaximizeOrRestoreClickListener,
+ mOnLeftSnapClickListener,
+ mOnRightSnapClickListener,
+ hovered -> {
+ mIsMaximizeMenuHovered = hovered;
+ onMaximizeHoverStateChanged();
+ return null;
+ }
+ );
+ }
+
+ /** Set whether the app header's maximize button is hovered. */
+ void setAppHeaderMaximizeButtonHovered(boolean hovered) {
+ mIsAppHeaderMaximizeButtonHovered = hovered;
+ onMaximizeHoverStateChanged();
+ }
+
+ /**
+ * Called when either one of the maximize button in the app header or the maximize menu has
+ * changed its hover state.
+ */
+ void onMaximizeHoverStateChanged() {
+ if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) {
+ // Neither is hovered, close the menu.
+ if (isMaximizeMenuActive()) {
+ mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS);
+ }
+ return;
+ }
+ // At least one of the two is hovered, cancel the close if needed.
+ mHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
}
/**
@@ -749,11 +862,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setCaptionHeight(mResult.mCaptionHeight)
.setDisplayController(mDisplayController)
.setSplitScreenController(splitScreenController)
+ .setBrowserLinkAvailable(browserLinkAvailable())
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
+ @VisibleForTesting
+ boolean browserLinkAvailable() {
+ return mCapturedLink != null && !mCapturedLink.mExpired;
+ }
+
/**
* Close the handle menu window.
*/
@@ -992,34 +1111,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setAnimatingTaskResize(animatingTaskResize);
}
- /** Called when there is a {@Link ACTION_HOVER_EXIT} on the maximize window button. */
- void onMaximizeWindowHoverExit() {
+ /**
+ * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
+ */
+ void onMaximizeButtonHoverExit() {
((AppHeaderViewHolder) mWindowDecorViewHolder)
.onMaximizeWindowHoverExit();
}
- /** Called when there is a {@Link ACTION_HOVER_ENTER} on the maximize window button. */
- void onMaximizeWindowHoverEnter() {
+ /**
+ * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
+ */
+ void onMaximizeButtonHoverEnter() {
((AppHeaderViewHolder) mWindowDecorViewHolder)
.onMaximizeWindowHoverEnter();
}
- /** Called when there is a {@Link ACTION_HOVER_ENTER} on a view in the maximize menu. */
- void onMaximizeMenuHoverEnter(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverEnter(id, ev);
- }
-
- /** Called when there is a {@Link ACTION_HOVER_MOVE} on a view in the maximize menu. */
- void onMaximizeMenuHoverMove(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverMove(id, ev);
- }
-
- /** Called when there is a {@Link ACTION_HOVER_EXIT} on a view in the maximize menu. */
- void onMaximizeMenuHoverExit(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverExit(id, ev);
- }
-
-
@Override
public String toString() {
return "{"
@@ -1055,6 +1162,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @VisibleForTesting
+ static class CapturedLink {
+ private final long mTimeStamp;
+ private final Uri mUri;
+ private boolean mExpired;
+
+ CapturedLink(@NonNull Uri uri, long timeStamp) {
+ mUri = uri;
+ mTimeStamp = timeStamp;
+ mExpired = false;
+ }
+
+ void setExpired() {
+ mExpired = true;
+ }
+ }
+
+
+ /** Listener for the handle menu's "Open in browser" button */
+ interface OpenInBrowserClickListener {
+
+ /** Inform the implementing class that the "Open in browser" button has been clicked */
+ void onClick(DesktopModeWindowDecoration decoration, Uri uri);
+ }
+
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
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 fe1c9c3cce66..2fd3eaa37ec3 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
@@ -28,10 +28,12 @@ import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
+import androidx.annotation.NonNull;
+
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
@@ -106,13 +108,15 @@ public class DragPositioningCallbackUtility {
repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
? candidateBottom : oldBottom;
}
- // If width or height are negative or less than the minimum width or height, revert the
+ // If width or height are negative or exceeding the width or height constraints, revert the
// respective bounds to use previous bound dimensions.
- if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) {
+ if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController,
+ windowDecoration)) {
repositionTaskBounds.right = oldRight;
repositionTaskBounds.left = oldLeft;
}
- if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
+ if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController,
+ windowDecoration)) {
repositionTaskBounds.top = oldTop;
repositionTaskBounds.bottom = oldBottom;
}
@@ -174,6 +178,30 @@ public class DragPositioningCallbackUtility {
return result;
}
+ private static boolean isExceedingWidthConstraint(@NonNull Rect repositionTaskBounds,
+ Rect maxResizeBounds, DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ // Check if width is less than the minimum width constraint.
+ if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) {
+ return true;
+ }
+ // Check if width is more than the maximum resize bounds on desktop windowing mode.
+ return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
+ && repositionTaskBounds.width() > maxResizeBounds.width();
+ }
+
+ private static boolean isExceedingHeightConstraint(@NonNull Rect repositionTaskBounds,
+ Rect maxResizeBounds, DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ // Check if height is less than the minimum height constraint.
+ if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
+ return true;
+ }
+ // Check if height is more than the maximum resize bounds on desktop windowing mode.
+ return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
+ && repositionTaskBounds.height() > maxResizeBounds.height();
+ }
+
private static float getMinWidth(DisplayController displayController,
WindowDecoration windowDecoration) {
return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
@@ -210,7 +238,7 @@ public class DragPositioningCallbackUtility {
private static float getDefaultMinSize(DisplayController displayController,
WindowDecoration windowDecoration) {
- float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
+ float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
.densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
return windowDecoration.mTaskInfo.defaultMinSize * density;
}
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 d902444d4b15..32df8b3b2c7c 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
@@ -55,7 +55,7 @@ import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import android.window.InputTransferToken;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -468,8 +468,7 @@ class DragResizeInputListener implements AutoCloseable {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getDisplayId(), e.getDeviceId(),
- e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
- e.getYCursorPosition());
+ e.getPointerId(/*pointerIndex=*/0), e.getX(), e.getY());
result = true;
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index b5d1d4a76342..ba5f0791a010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,9 +33,6 @@ import android.graphics.Region;
import android.util.Size;
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
import com.android.wm.shell.R;
import java.util.Objects;
@@ -44,11 +41,6 @@ import java.util.Objects;
* Geometry for a drag resize region for a particular window.
*/
final class DragResizeWindowGeometry {
- // TODO(b/337264971) clean up when no longer needed
- @VisibleForTesting static final boolean DEBUG = true;
- // The additional width to apply to edge resize bounds just for logging when a touch is
- // close.
- @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
private final int mTaskCornerRadius;
private final Size mTaskSize;
// The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -60,8 +52,6 @@ final class DragResizeWindowGeometry {
private final @NonNull TaskCorners mFineTaskCorners;
// The bounds for each edge drag region, which can resize the task in one direction.
private final @NonNull TaskEdges mTaskEdges;
- // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
- private final @Nullable TaskEdges mDebugTaskEdges;
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -74,11 +64,6 @@ final class DragResizeWindowGeometry {
// Save touch areas for each edge.
mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
- if (DEBUG) {
- mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
- } else {
- mDebugTaskEdges = null;
- }
}
/**
@@ -120,13 +105,7 @@ final class DragResizeWindowGeometry {
*/
void union(@NonNull Region region) {
// Apply the edge resize regions.
- if (inDebugMode()) {
- // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
- // touch due to the size of the edge region.
- mDebugTaskEdges.union(region);
- } else {
- mTaskEdges.union(region);
- }
+ mTaskEdges.union(region);
if (enableWindowingEdgeDragResize()) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -219,10 +198,6 @@ final class DragResizeWindowGeometry {
@DragPositioningCallback.CtrlType
private int calculateEdgeResizeCtrlType(float x, float y) {
- if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
- && !mTaskEdges.contains((int) x, (int) y))) {
- return CTRL_TYPE_UNDEFINED;
- }
int ctrlType = CTRL_TYPE_UNDEFINED;
// mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
// sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -313,9 +288,7 @@ final class DragResizeWindowGeometry {
&& this.mResizeHandleThickness == other.mResizeHandleThickness
&& this.mFineTaskCorners.equals(other.mFineTaskCorners)
&& this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
- && (inDebugMode()
- ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
- : this.mTaskEdges.equals(other.mTaskEdges));
+ && this.mTaskEdges.equals(other.mTaskEdges);
}
@Override
@@ -326,11 +299,7 @@ final class DragResizeWindowGeometry {
mResizeHandleThickness,
mFineTaskCorners,
mLargeTaskCorners,
- (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
- }
-
- private boolean inDebugMode() {
- return DEBUG && mDebugTaskEdges != null;
+ mTaskEdges);
}
/**
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 df0836c1121d..7e44f32bcbeb 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
@@ -42,6 +42,7 @@ import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -81,6 +82,7 @@ class HandleMenu {
// those as well.
final Point mGlobalMenuPosition = new Point();
private final boolean mShouldShowWindowingPill;
+ private final boolean mShouldShowBrowserPill;
private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
@@ -101,7 +103,7 @@ class HandleMenu {
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
Bitmap appIcon, CharSequence appName, DisplayController displayController,
SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
- int captionHeight) {
+ boolean shouldShowBrowserPill, int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -113,6 +115,7 @@ class HandleMenu {
mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mShouldShowBrowserPill = shouldShowBrowserPill;
mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
@@ -170,6 +173,7 @@ class HandleMenu {
setupWindowingPill(handleMenu);
}
setupMoreActionsPill(handleMenu);
+ setupOpenInBrowserPill(handleMenu);
}
/**
@@ -228,6 +232,15 @@ class HandleMenu {
}
}
+ private void setupOpenInBrowserPill(View handleMenu) {
+ if (!mShouldShowBrowserPill) {
+ handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE);
+ return;
+ }
+ final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button);
+ browserButton.setOnClickListener(mOnClickListener);
+ }
+
/**
* Returns array of windowing icon color based on current UI theme. First element of the
* array is for inactive icons and the second is for active icons.
@@ -423,6 +436,10 @@ class HandleMenu {
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
}
+ if (!mShouldShowBrowserPill) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height);
+ }
return menuHeight;
}
@@ -457,6 +474,7 @@ class HandleMenu {
private int mCaptionHeight;
private DisplayController mDisplayController;
private SplitScreenController mSplitScreenController;
+ private boolean mShowBrowserPill;
Builder(@NonNull DesktopModeWindowDecoration parent) {
mParent = parent;
@@ -507,10 +525,15 @@ class HandleMenu {
return this;
}
+ Builder setBrowserLinkAvailable(Boolean showBrowserPill) {
+ mShowBrowserPill = showBrowserPill;
+ return this;
+ }
+
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mOnClickListener,
mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
- mShowWindowingPill, mCaptionHeight);
+ mShowWindowingPill, mShowBrowserPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 8c5d4a2c2ffb..25a829b44448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@ import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
+import android.widget.Button
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import com.android.wm.shell.R
@@ -72,6 +73,7 @@ class HandleMenuAnimator(
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+ private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -80,6 +82,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -94,6 +97,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -109,6 +113,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -125,6 +130,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -137,6 +143,7 @@ class HandleMenuAnimator(
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
+ openInBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -147,6 +154,9 @@ class HandleMenuAnimator(
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ openInBrowserPill.pivotX = menuWidth / 2f
+ openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -268,12 +278,50 @@ class HandleMenuAnimator(
// More Actions Content Opacity Animation
moreActionsPill.children.forEach {
animators +=
- ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateOpenInBrowserPill() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // Open in Browser Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // Open in Browser Button Opacity Animation
+ val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ animators +=
+ ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_CONTENT_ALPHA_OPEN_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
- }
}
private fun appInfoPillCollapse() {
@@ -379,6 +427,37 @@ class HandleMenuAnimator(
}
}
+ private fun openInBrowserPillClose() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Upward Open in Browser y-translation Animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
/**
* Runs the list of hide animators concurrently.
*
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 0470367015ea..5f9f8d6d1764 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
@@ -20,7 +20,6 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.ColorInt
-import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.ColorStateList
@@ -28,6 +27,7 @@ import android.content.res.Resources
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.PointF
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
@@ -37,16 +37,17 @@ import android.graphics.drawable.shapes.RoundRectShape
import android.util.StateSet
import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_HOVER_ENTER
+import android.view.MotionEvent.ACTION_HOVER_EXIT
+import android.view.MotionEvent.ACTION_HOVER_MOVE
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.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
@@ -64,10 +65,10 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHo
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
-
/**
* Menu that appears when user long clicks the maximize button. Gives the user the option to
* maximize the task or snap the task to the right or left half of the screen.
@@ -77,9 +78,6 @@ class MaximizeMenu(
private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
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() }
@@ -102,9 +100,19 @@ class MaximizeMenu(
}
/** Creates and shows the maximize window. */
- fun show() {
+ fun show(
+ onMaximizeClickListener: OnTaskActionClickListener,
+ onLeftSnapClickListener: OnTaskActionClickListener,
+ onRightSnapClickListener: OnTaskActionClickListener,
+ onHoverListener: (Boolean) -> Unit
+ ) {
if (maximizeMenu != null) return
- createMaximizeMenu()
+ createMaximizeMenu(
+ onMaximizeClickListener = onMaximizeClickListener,
+ onLeftSnapClickListener = onLeftSnapClickListener,
+ onRightSnapClickListener = onRightSnapClickListener,
+ onHoverListener = onHoverListener
+ )
maximizeMenuView?.animateOpenMenu()
}
@@ -117,7 +125,12 @@ class MaximizeMenu(
}
/** Create a maximize menu that is attached to the display area. */
- private fun createMaximizeMenu() {
+ private fun createMaximizeMenu(
+ onMaximizeClickListener: OnTaskActionClickListener,
+ onLeftSnapClickListener: OnTaskActionClickListener,
+ onRightSnapClickListener: OnTaskActionClickListener,
+ onHoverListener: (Boolean) -> Unit
+ ) {
val t = transactionSupplier.get()
val builder = SurfaceControl.Builder()
rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder)
@@ -146,11 +159,19 @@ class MaximizeMenu(
context = decorWindowContext,
menuHeight = menuHeight,
menuPadding = menuPadding,
- onClickListener = onClickListener,
- onTouchListener = onTouchListener,
- onGenericMotionListener = onGenericMotionListener,
).also { menuView ->
+ val taskId = taskInfo.taskId
menuView.bind(taskInfo)
+ menuView.onMaximizeClickListener = {
+ onMaximizeClickListener.onClick(taskId, "maximize_menu_option")
+ }
+ menuView.onLeftSnapClickListener = {
+ onLeftSnapClickListener.onClick(taskId, "left_snap_option")
+ }
+ menuView.onRightSnapClickListener = {
+ onRightSnapClickListener.onClick(taskId, "right_snap_option")
+ }
+ menuView.onMenuHoverListener = onHoverListener
viewHost.setView(menuView.rootView, lp)
}
@@ -198,56 +219,6 @@ class MaximizeMenu(
}
/**
- * Called when a [MotionEvent.ACTION_HOVER_ENTER] is triggered on any of the menu's views.
- *
- * TODO(b/346440693): this is only needed for the left/right snap options that don't support
- * selector states to manage its hover state. Look into whether that can be added to avoid
- * manually tracking hover enter/exit motion events. Also because those button colors/states
- * aren't updating correctly for pressed, focused and selected states.
- * See also [onMaximizeMenuHoverMove] and [onMaximizeMenuHoverExit].
- */
- fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) {
- setSnapButtonsColorOnHover(viewId, ev)
- }
-
- /** Called when a [MotionEvent.ACTION_HOVER_MOVE] is triggered on any of the menu's views. */
- fun onMaximizeMenuHoverMove(viewId: Int, ev: MotionEvent) {
- setSnapButtonsColorOnHover(viewId, ev)
- }
-
- /** Called when a [MotionEvent.ACTION_HOVER_EXIT] is triggered on any of the menu's views. */
- fun onMaximizeMenuHoverExit(id: Int, ev: MotionEvent) {
- val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return
- val snapOptionsHeight = maximizeMenuView?.snapOptionsHeight ?: return
- val inSnapMenuBounds = ev.x >= 0 && ev.x <= snapOptionsWidth &&
- ev.y >= 0 && ev.y <= snapOptionsHeight
-
- if (id == R.id.maximize_menu_snap_menu_layout && !inSnapMenuBounds) {
- // After exiting the snap menu layout area, checks to see that user is not still
- // hovering within the snap menu layout bounds which would indicate that the user is
- // hovering over a snap button within the snap menu layout rather than having exited.
- maximizeMenuView?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.NONE)
- }
- }
-
- private fun setSnapButtonsColorOnHover(viewId: Int, ev: MotionEvent) {
- val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return
- val snapMenuCenter = snapOptionsWidth / 2
- when {
- viewId == R.id.maximize_menu_snap_left_button ||
- (viewId == R.id.maximize_menu_snap_menu_layout && ev.x <= snapMenuCenter) -> {
- maximizeMenuView
- ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.LEFT)
- }
- viewId == R.id.maximize_menu_snap_right_button ||
- (viewId == R.id.maximize_menu_snap_menu_layout && ev.x > snapMenuCenter) -> {
- maximizeMenuView
- ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.RIGHT)
- }
- }
- }
-
- /**
* The view within the Maximize Menu, presents maximize, restore and snap-to-side options for
* resizing a Task.
*/
@@ -255,12 +226,11 @@ class MaximizeMenu(
context: Context,
private val menuHeight: Int,
private val menuPadding: Int,
- onClickListener: OnClickListener,
- onTouchListener: OnTouchListener,
- onGenericMotionListener: OnGenericMotionListener,
) {
- val rootView: View = LayoutInflater.from(context)
- .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */)
+ val rootView = LayoutInflater.from(context)
+ .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup
+ private val container = requireViewById(R.id.container)
+ private val overlay = requireViewById(R.id.maximize_menu_overlay)
private val maximizeText =
requireViewById(R.id.maximize_menu_maximize_window_text) as TextView
private val maximizeButton =
@@ -285,30 +255,63 @@ class MaximizeMenu(
private val fillRadius = context.resources
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
+ private val hoverTempRect = Rect()
private val openMenuAnimatorSet = AnimatorSet()
private lateinit var taskInfo: RunningTaskInfo
private lateinit var style: MenuStyle
- /** The width of the snap menu option view, including both left and right snaps. */
- val snapOptionsWidth: Int
- get() = snapButtonsLayout.width
- /** The height of the snap menu option view, including both left and right snaps .*/
- val snapOptionsHeight: Int
- get() = snapButtonsLayout.height
+ /** Invoked when the maximize or restore option is clicked. */
+ var onMaximizeClickListener: (() -> Unit)? = null
+ /** Invoked when the left snap option is clicked. */
+ var onLeftSnapClickListener: (() -> Unit)? = null
+ /** Invoked when the right snap option is clicked. */
+ var onRightSnapClickListener: (() -> Unit)? = null
+ /** Invoked whenever the hover state of the menu changes. */
+ var onMenuHoverListener: ((Boolean) -> Unit)? = null
init {
- // TODO(b/346441962): encapsulate menu hover enter/exit logic inside this class and
- // expose only what is actually relevant to outside classes so that specific checks
- // against resource IDs aren't needed outside this class.
- rootView.setOnGenericMotionListener(onGenericMotionListener)
- rootView.setOnTouchListener(onTouchListener)
- maximizeButton.setOnClickListener(onClickListener)
- maximizeButton.setOnGenericMotionListener(onGenericMotionListener)
- snapRightButton.setOnClickListener(onClickListener)
- snapRightButton.setOnGenericMotionListener(onGenericMotionListener)
- snapLeftButton.setOnClickListener(onClickListener)
- snapLeftButton.setOnGenericMotionListener(onGenericMotionListener)
- snapButtonsLayout.setOnGenericMotionListener(onGenericMotionListener)
+ overlay.setOnHoverListener { _, event ->
+ // The overlay covers the entire menu, so it's a convenient way to monitor whether
+ // the menu is hovered as a whole or not.
+ when (event.action) {
+ ACTION_HOVER_ENTER -> onMenuHoverListener?.invoke(true)
+ ACTION_HOVER_EXIT -> onMenuHoverListener?.invoke(false)
+ }
+
+ // Also check if the hover falls within the snap options layout, to manually
+ // set the left/right state based on the event's position.
+ // TODO(b/346440693): this manual hover tracking is needed for left/right snap
+ // because its view/background(s) don't support selector states. Look into whether
+ // that can be added to avoid manual tracking. Also because these button
+ // colors/state logic is only being applied on hover events, but there's pressed,
+ // focused and selected states that should be responsive too.
+ val snapLayoutBoundsRelToOverlay = hoverTempRect.also { rect ->
+ snapButtonsLayout.getDrawingRect(rect)
+ rootView.offsetDescendantRectToMyCoords(snapButtonsLayout, rect)
+ }
+ if (event.action == ACTION_HOVER_ENTER || event.action == ACTION_HOVER_MOVE) {
+ if (snapLayoutBoundsRelToOverlay.contains(event.x.toInt(), event.y.toInt())) {
+ // Hover is inside the snap layout, anything left of center is the left
+ // snap, and anything right of center is right snap.
+ val layoutCenter = snapLayoutBoundsRelToOverlay.centerX()
+ if (event.x < layoutCenter) {
+ updateSplitSnapSelection(SnapToHalfSelection.LEFT)
+ } else {
+ updateSplitSnapSelection(SnapToHalfSelection.RIGHT)
+ }
+ } else {
+ // Any other hover is outside the snap layout, so neither is selected.
+ updateSplitSnapSelection(SnapToHalfSelection.NONE)
+ }
+ }
+
+ // Don't consume the event to allow child views to receive the event too.
+ return@setOnHoverListener false
+ }
+
+ maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() }
+ snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() }
+ snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() }
// To prevent aliasing.
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
@@ -351,7 +354,7 @@ class MaximizeMenu(
val value = animatedValue as Float
val topPadding = menuPadding -
((1 - value) * menuHeight).toInt()
- rootView.setPadding(menuPadding, topPadding,
+ container.setPadding(menuPadding, topPadding,
menuPadding, menuPadding)
}
},
@@ -410,7 +413,7 @@ class MaximizeMenu(
}
/** Update the view state to a new snap to half selection. */
- fun updateSplitSnapSelection(selection: SnapToHalfSelection) {
+ private fun updateSplitSnapSelection(selection: SnapToHalfSelection) {
when (selection) {
SnapToHalfSelection.NONE -> deactivateSnapOptions()
SnapToHalfSelection.LEFT -> activateSnapOption(activateLeft = true)
@@ -638,13 +641,41 @@ class MaximizeMenu(
private const val ELEVATION_ANIMATION_DURATION_MS = 50L
private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
private const val MENU_Z_TRANSLATION = 1f
- 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 ||
- viewId == R.id.maximize_menu_snap_menu_layout ||
- viewId == R.id.maximize_menu_snap_menu_layout
- }
+ }
+}
+
+/** A factory interface to create a [MaximizeMenu]. */
+interface MaximizeMenuFactory {
+ fun create(
+ syncQueue: SyncTransactionQueue,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayController: DisplayController,
+ taskInfo: RunningTaskInfo,
+ decorWindowContext: Context,
+ menuPosition: PointF,
+ transactionSupplier: Supplier<Transaction>
+ ): MaximizeMenu
+}
+
+/** A [MaximizeMenuFactory] implementation that creates a [MaximizeMenu]. */
+object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
+ override fun create(
+ syncQueue: SyncTransactionQueue,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayController: DisplayController,
+ taskInfo: RunningTaskInfo,
+ decorWindowContext: Context,
+ menuPosition: PointF,
+ transactionSupplier: Supplier<Transaction>
+ ): MaximizeMenu {
+ return MaximizeMenu(
+ syncQueue,
+ rootTdaOrganizer,
+ displayController,
+ taskInfo,
+ decorWindowContext,
+ menuPosition,
+ transactionSupplier
+ )
}
}
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 5fce5d228d71..153221150992 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
@@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW;
+
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -31,6 +33,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.transition.Transitions;
@@ -56,6 +59,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
@@ -64,22 +68,24 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions) {
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions);
+ SurfaceControl.Transaction::new, transitions, interactionJankMonitor);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+ InteractionJankMonitor interactionJankMonitor) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
+ mInteractionJankMonitor = interactionJankMonitor;
}
@Override
@@ -89,6 +95,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
+ // Capture CUJ for re-sizing window in DW mode.
+ mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
+ mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -146,6 +155,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
// won't be called.
resetVeilIfVisible();
}
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
} else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
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 216990c35247..e86f6a162c2d 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
@@ -19,6 +19,7 @@ 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.content.res.Configuration.DENSITY_DPI_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -53,9 +54,10 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
@@ -759,9 +761,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
void addOrUpdate(WindowContainerTransaction wct) {
- wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects);
+ final @InsetsSource.Flags int captionSourceFlags =
+ Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
+ wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
+ captionSourceFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
- mBoundingRects);
+ mBoundingRects, 0 /* flags */);
}
void remove(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
new file mode 100644
index 000000000000..14b9e7f71622
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.common
+
+/** A callback to be invoked when a Task's window decor element is clicked. */
+fun interface OnTaskActionClickListener {
+ /**
+ * Called when a task's decor element has been clicked.
+ *
+ * @param taskId the id of the task.
+ * @param tag a readable identifier for the element.
+ */
+ fun onClick(taskId: Int, tag: String)
+}
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 a0a61fe2cf72..d0e8215e662e 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
@@ -117,12 +117,10 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
/**
* Checks that all parts of the screen are covered at the start and end of the transition
- *
- * TODO b/197726599 Prevents all states from being checked
*/
@Presubmit
@Test
- fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
+ fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered()
/** Checks [pipApp] window remains visible and on top throughout the transition */
@Presubmit
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 35ed8de3a464..7873a85d515d 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
@@ -65,7 +65,7 @@ open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 879034f32514..5c539a5e8a03 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -73,7 +73,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.enterFullscreen()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index d485b82f5ddb..430f80b9a927 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -20,18 +20,23 @@ import android.tools.flicker.AssertionInvocationGroup
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
-import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
+import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
-import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
-import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop
+import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
import android.tools.flicker.extractors.ITransitionMatcher
import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.extractors.TaggedCujTransitionMatcher
+import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder
+import android.tools.traces.events.CujType
import android.tools.traces.wm.Transition
import android.tools.traces.wm.TransitionType
@@ -48,6 +53,7 @@ class DesktopModeFlickerScenarios {
transitions: Collection<Transition>
): Collection<Transition> {
return transitions.filter {
+ // TODO(351168217) Use jank CUJ to extract a longer trace
it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
}
}
@@ -60,11 +66,14 @@ class DesktopModeFlickerScenarios {
AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
AppWindowHasDesktopModeInitialBoundsAtTheEnd(
Components.DESKTOP_MODE_APP
- )
+ ),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ // Use this scenario for closing an app in desktop windowing, except the last app. For the
+ // last app use CLOSE_LAST_APP scenario
val CLOSE_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_APP"),
@@ -75,7 +84,13 @@ class DesktopModeFlickerScenarios {
override fun findAll(
transitions: Collection<Transition>
): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.CLOSE }
+ // In case there are multiple windows closing, filter out the
+ // last window closing. It should use the CLOSE_LAST_APP
+ // scenario below.
+ return transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .sortedByDescending { it.id }
+ .drop(1)
}
}
),
@@ -100,19 +115,19 @@ class DesktopModeFlickerScenarios {
transitions: Collection<Transition>
): Collection<Transition> {
val lastTransition =
- transitions.findLast { it.type == TransitionType.CLOSE }
- return if (lastTransition != null) listOf(lastTransition)
- else emptyList()
+ transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
}
}
),
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowMovesToTop()
+ AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -121,24 +136,29 @@ class DesktopModeFlickerScenarios {
FlickerConfigEntry(
scenarioId = ScenarioId("CORNER_RESIZE"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
- it.type == TransitionType.CHANGE
- }
- }
- }
- ),
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
+ )
+
+ val CORNER_RESIZE_TO_MINIMUM_SIZE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
assertions =
- listOf(
- AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700))
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
new file mode 100644
index 000000000000..6319cf74ed8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.service.desktopmode.flicker
+
+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.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in
+ * landscape mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizeLandscape : ResizeAppWithCornerResize(
+ rotation = Rotation.ROTATION_90,
+ horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
new file mode 100644
index 000000000000..431f6e3d3ea2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.service.desktopmode.flicker
+
+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.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in portrait mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizePortrait : ResizeAppWithCornerResize(horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
new file mode 100644
index 000000000000..20e2167c28f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base scenario test for maximize app window CUJ in desktop mode. */
+@Ignore("Base Test Class")
+abstract class MaximizeAppWindow
+{
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
+ Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWindow() {
+ testApp.maximiseDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
index ac9089a5c1bd..136cf378aa09 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
@@ -38,7 +38,9 @@ import org.junit.Test
@Ignore("Base Test Class")
abstract class ResizeAppWithCornerResize
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+ val horizontalChange: Int = 50,
+ val verticalChange: Int = -50) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
@@ -46,7 +48,9 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@Before
fun setup() {
@@ -58,7 +62,11 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun resizeAppWithCornerResize() {
- testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50)
+ testApp.cornerResize(wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.RIGHT_TOP,
+ horizontalChange,
+ verticalChange)
}
@After
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 c671fbe39ac5..b5a6d83afd05 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
@@ -48,6 +48,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ // TODO: b/349075982 - Remove once launcher rotation and checks are stable.
+ tapl.setExpectedRotationCheckEnabled(false)
SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
index 0fe7a16be851..35b2f56bca92 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -37,17 +37,51 @@ filegroup {
"src/**/B*.kt",
"src/**/C*.kt",
"src/**/D*.kt",
- "src/**/E*.kt",
],
}
filegroup {
name: "WMShellFlickerTestsSplitScreenGroup2-src",
srcs: [
+ "src/**/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroup3-src",
+ srcs: [
+ "src/**/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroupOther-src",
+ srcs: [
"src/**/*.kt",
],
}
+java_library {
+ name: "WMShellFlickerTestsSplitScreenBase",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "wm-shell-flicker-utils",
+ "androidx.test.ext.junit",
+ "flickertestapplib",
+ "flickerlib",
+ "flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "wm-flicker-common-assertions",
+ "launcher-helper-lib",
+ "launcher-aosp-tapl",
+ ],
+}
+
android_test {
name: "WMShellFlickerTestsSplitScreenGroup1",
defaults: ["WMShellFlickerTestsDefault"],
@@ -56,10 +90,12 @@ android_test {
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerTestsSplitScreenGroup1-src",
],
- static_libs: ["WMShellFlickerTestsBase"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellFlickerTestsSplitScreenBase",
+ ],
data: ["trace_config/*"],
}
@@ -71,12 +107,50 @@ android_test {
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerTestsSplitScreenGroup2-src",
],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellFlickerTestsSplitScreenBase",
+ ],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroup3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenGroup3-src",
+ ],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellFlickerTestsSplitScreenBase",
+ ],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroupOther",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenGroupOther-src",
+ ],
exclude_srcs: [
":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
+ ":WMShellFlickerTestsSplitScreenGroup3-src",
+ ],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellFlickerTestsSplitScreenBase",
],
- static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 13f95ccea640..6b6954289a34 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -46,6 +46,7 @@ android_test {
"androidx.dynamicanimation_dynamicanimation",
"dagger2",
"frameworks-base-testutils",
+ "kotlin-test",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"mockito-kotlin2",
@@ -58,6 +59,7 @@ android_test {
"guava-android-testlib",
"com.android.window.flags.window-aconfig-java",
"platform-test-annotations",
+ "flag-junit",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f9b4108bc8c2..e91828be4ebe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -45,6 +45,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -63,6 +64,8 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -70,6 +73,7 @@ import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -371,7 +375,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
clearInvocations(mCompatUI);
@@ -381,7 +385,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Not show size compat UI if task is not visible.
clearInvocations(mCompatUI);
@@ -391,11 +395,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -410,7 +414,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
// Task listener sent to compat UI is null if top activity isn't eligible for letterbox
// education.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null if top activity is eligible for letterbox education and task
// is visible.
@@ -421,7 +425,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Task listener is null if task is invisible.
clearInvocations(mCompatUI);
@@ -431,11 +435,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -451,7 +455,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
// Task listener sent to compat UI is null if top activity doesn't request a camera
// compat control.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null when request a camera compat control for a visible task.
clearInvocations(mCompatUI);
@@ -462,7 +466,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// CompatUIController#onCompatInfoChanged is called when requested state for a camera
// compat control changes for a visible task.
@@ -474,7 +478,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo3.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, taskListener);
// CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
// mode for a visible task that has a compat control.
@@ -487,7 +491,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo4.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo4);
- verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo4, taskListener);
// Task linster is null when a camera compat control is dimissed for a visible task.
clearInvocations(mCompatUI);
@@ -498,7 +502,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_DISMISSED;
taskInfo5.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo5);
- verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo5, null /* taskListener */);
// Task linster is null when request a camera compat control for a invisible task.
clearInvocations(mCompatUI);
@@ -509,11 +513,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo6.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo6);
- verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo6, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -640,7 +644,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId);
+ mOrganizer.onSizeCompatRestartButtonClicked(
+ new CompatUIEvents.SizeCompatRestartButtonClicked(task1.taskId));
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
@@ -687,6 +692,25 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
}
+ @Test
+ public void testTaskVanishedCallback() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1];
+ ShellTaskOrganizer.TaskVanishedListener listener =
+ new ShellTaskOrganizer.TaskVanishedListener() {
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ vanishedTasks[0] = taskInfo;
+ }
+ };
+ mOrganizer.addTaskVanishedListener(listener);
+ mOrganizer.onTaskVanished(task1);
+
+ assertEquals(vanishedTasks[0], task1);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -694,4 +718,13 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo.isVisible = true;
return taskInfo;
}
+
+ private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener listener) {
+ final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class);
+ verify(mCompatUI).onCompatInfoChanged(capture.capture());
+ final CompatUIInfo captureValue = capture.getValue();
+ assertEquals(captureValue.getTaskInfo(), taskInfo);
+ assertEquals(captureValue.getListener(), listener);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 51a20ee9d090..a2df22c5468f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -26,7 +26,7 @@ import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index bd20c1143262..55b6bd278f20 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -20,9 +20,13 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation;
import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -32,9 +36,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.animation.Animator;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -130,7 +139,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
@Test
public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
.build();
info.setAnimationOptions(TransitionInfo.AnimationOptions
.makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -148,7 +157,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
@Test
public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
.build();
info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions
.makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -161,4 +170,140 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
// An invalid custom animation is equivalent to jump-cut.
assertEquals(0, animator.getDuration());
}
+
+ @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+ @Test
+ public void testCalculateParentBounds_flagDisabled() {
+ final Rect parentBounds = new Rect(0, 0, 2000, 2000);
+ final Rect primaryBounds = new Rect();
+ final Rect secondaryBounds = new Rect();
+ parentBounds.splitVertically(primaryBounds, secondaryBounds);
+
+ final TransitionInfo.Change change = createChange(0 /* flags */);
+ change.setStartAbsBounds(secondaryBounds);
+
+ final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */);
+ boundsAnimationChange.setStartAbsBounds(primaryBounds);
+ boundsAnimationChange.setEndAbsBounds(primaryBounds);
+ final Rect actualParentBounds = new Rect();
+
+ calculateParentBounds(change, boundsAnimationChange, actualParentBounds);
+
+ assertEquals(parentBounds, actualParentBounds);
+
+ actualParentBounds.setEmpty();
+
+ boundsAnimationChange.setStartAbsBounds(secondaryBounds);
+ boundsAnimationChange.setEndAbsBounds(primaryBounds);
+
+ calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds);
+
+ assertEquals(parentBounds, actualParentBounds);
+ }
+
+ // TODO(b/243518738): Rewrite with TestParameter
+ @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+ @Test
+ public void testCalculateParentBounds_flagEnabled() {
+ TransitionInfo.Change change;
+ final TransitionInfo.Change stubChange = createChange(0 /* flags */);
+ final Rect actualParentBounds = new Rect();
+ Rect parentBounds = new Rect(0, 0, 2000, 2000);
+ Rect endAbsBounds = new Rect(0, 0, 2000, 2000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(0, 0) /* endRelOffset */,
+ endAbsBounds,
+ new Point() /* endParentSize */
+ );
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertTrue("Parent bounds must be empty because end parent size is not set.",
+ actualParentBounds.isEmpty());
+
+ String testString = "Parent start with (0, 0)";
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Container not start with (0, 0)";
+ parentBounds = new Rect(0, 0, 2000, 2000);
+ endAbsBounds = new Rect(1000, 500, 2000, 1500);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container on the right";
+ parentBounds = new Rect(1000, 0, 2000, 2000);
+ endAbsBounds = new Rect(1000, 500, 1500, 1500);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container on the bottom";
+ parentBounds = new Rect(0, 1000, 2000, 2000);
+ endAbsBounds = new Rect(500, 1500, 1500, 2000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container in the middle";
+ parentBounds = new Rect(500, 500, 1500, 1500);
+ endAbsBounds = new Rect(1000, 500, 1500, 1000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+ }
+
+ @Test
+ public void testShouldUseJumpCutForAnimation() {
+ final Animation noopAnimation = new AlphaAnimation(0f, 1f);
+ assertTrue("Animation without duration should use jump cut.",
+ shouldUseJumpCutForAnimation(noopAnimation));
+
+ final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
+ alphaAnimation.setDuration(100);
+ assertFalse("Animation with duration should not use jump cut.",
+ shouldUseJumpCutForAnimation(alphaAnimation));
+ }
+
+ @NonNull
+ private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
+ @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
+ final TransitionInfo.Change change = createChange(0 /* flags */);
+ change.setEndRelOffset(endRelOffset.x, endRelOffset.y);
+ change.setEndAbsBounds(endAbsBounds);
+ change.setEndParentSize(endParentSize.x, endParentSize.y);
+ return change;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 0b2265d4ce9c..c18d7ec821b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -31,6 +32,7 @@ import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
spyOn(mFinishCallback);
}
- /** Creates a mock {@link TransitionInfo.Change}. */
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ */
static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) {
+ return createChange(flags, TRANSIT_NONE);
+ }
+
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ * @param mode the transition mode of the change
+ */
+ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags,
+ @WindowManager.TransitionType int mode) {
TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class),
mock(SurfaceControl.class));
c.setFlags(flags);
+ c.setMode(mode);
return c;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 636c6326d213..f5847cc27071 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -70,7 +70,7 @@ public class DividerViewTest extends ShellTestCase {
mContext,
configuration, mCallbacks);
splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
- mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ mDividerView = spy(splitWindowManager.getDividerView());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 4cd2a366f5eb..ecaf970ae389 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.compatui
+import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.R
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import org.junit.Assert.assertFalse
@@ -34,26 +36,55 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {
-
@Test
- fun testIsSingleTopActivityTranslucent() {
- assertTrue(isSingleTopActivityTranslucent(
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = true
numActivities = 1
}))
- assertFalse(isSingleTopActivityTranslucent(
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = true
numActivities = 0
}))
- assertFalse(isSingleTopActivityTranslucent(
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }))
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = false
numActivities = 1
}))
}
-} \ No newline at end of file
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityStyleFloating = true
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() {
+ val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = baseComponent
+ }))
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 9c008647104a..fc7a7770b8ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -38,6 +38,9 @@ import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -45,6 +48,7 @@ import android.view.accessibility.AccessibilityManager;
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.DisplayController;
@@ -55,6 +59,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -63,6 +68,7 @@ import dagger.Lazy;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,6 +88,10 @@ public class CompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
@@ -168,28 +178,32 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
// Verify that the compat controls are added with non-null task listener.
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -202,7 +216,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -213,9 +227,9 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- /* taskListener= */ null);
+ /* taskListener= */ null));
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
@@ -223,6 +237,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
@@ -230,7 +245,7 @@ public class CompatUIControllerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -240,7 +255,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -253,6 +268,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
@@ -260,7 +276,7 @@ public class CompatUIControllerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -270,7 +286,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -282,7 +298,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -296,6 +312,7 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayAdded() {
mController.onDisplayAdded(DISPLAY_ID);
mController.onDisplayAdded(DISPLAY_ID + 1);
@@ -305,11 +322,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -328,9 +345,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayConfigurationChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
@@ -346,10 +364,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
@@ -373,9 +392,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnImeShowHide() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
@@ -387,7 +407,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -405,9 +425,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after keyguard becomes showing.
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -419,7 +440,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -437,9 +458,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -466,9 +488,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -495,6 +518,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutRecreatedIfNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -502,14 +526,15 @@ public class CompatUIControllerTest extends ShellTestCase {
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(2))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutNotRecreatedIfNotNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -517,14 +542,15 @@ public class CompatUIControllerTest extends ShellTestCase {
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(1))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() {
// Simulate user aspect ratio button being shown for previous task
mController.setHasShownUserAspectRatioSettingsButton(true);
@@ -545,6 +571,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -606,6 +633,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -634,6 +662,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -674,7 +703,7 @@ public class CompatUIControllerTest extends ShellTestCase {
CAMERA_COMPAT_CONTROL_HIDDEN);
taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
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 cd3e8cb0e8e1..33d69f5c34c8 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
@@ -23,6 +23,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_S
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@@ -31,6 +32,9 @@ import android.app.ActivityManager;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,16 +44,20 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
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;
@@ -70,8 +78,12 @@ public class CompatUILayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@@ -101,6 +113,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForRestartButton() {
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
@@ -117,6 +130,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForRestartButton() {
doNothing().when(mWindowManager).onRestartButtonLongClicked();
@@ -127,6 +141,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -137,6 +152,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -145,16 +161,17 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -163,16 +180,17 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -181,12 +199,12 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show */ false);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraTreatmentButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -198,6 +216,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraDismissButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -208,6 +227,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForCameraCompatHint() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -229,4 +249,15 @@ public class CompatUILayoutTest extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
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 41a81c1a9921..eb3da8f65b5d 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
@@ -25,6 +25,7 @@ import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -42,6 +43,9 @@ import android.app.CameraCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -60,6 +64,8 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import junit.framework.Assert;
@@ -85,12 +91,16 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
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;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@@ -129,6 +139,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
@@ -174,6 +185,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateCameraCompatControl() {
// Doesn't create layout if show is false.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -212,6 +224,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -224,6 +237,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -315,6 +329,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.createLayout(/* canShow= */ false);
@@ -347,6 +362,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -366,6 +382,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -390,6 +407,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.mLayout = null;
@@ -415,6 +433,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -423,37 +442,38 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show= */ false);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraTreatmentButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
@@ -468,8 +488,9 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonLongClicked_showHint() {
- // Not create hint popup.
+ // Not create hint popup.
mWindowManager.mHasSizeCompat = true;
mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -483,6 +504,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraControlLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -498,6 +520,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
@@ -506,6 +529,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testShouldShowSizeCompatRestartButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
@@ -558,4 +582,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index 172c263ab0f6..e8191db13084 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,6 +37,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +60,10 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
private View mDismissButton;
private View mDialogContainer;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -66,6 +76,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_education_dialog_container));
@@ -76,6 +87,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -83,6 +95,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClicked() {
assertTrue(mLayout.performClick());
@@ -90,6 +103,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -97,6 +111,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index a60a1cbb435f..b5664ac113fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.compatui;
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +38,9 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -61,6 +65,7 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -116,6 +121,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -153,6 +162,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -162,6 +172,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, /* isDocked */ true);
@@ -172,6 +183,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -182,6 +194,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -192,6 +205,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -238,6 +252,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ false);
@@ -271,6 +286,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -288,6 +304,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -316,6 +333,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -329,6 +347,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_canShowFalse_doesNothing() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -343,6 +362,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -364,6 +384,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease_animationIsCancelled() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -374,6 +395,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testDeviceThemeChange_educationDialogUnseen_recreated() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 4f71b83179b1..0da14d673732 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -23,6 +25,9 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.TaskInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -34,6 +39,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -62,6 +68,10 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
@Mock
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -74,6 +84,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertNotNull(mMoveUpButton);
assertNotNull(mMoveDownButton);
@@ -82,6 +93,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
false, /* letterboxVerticalPosition */
@@ -94,6 +106,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
@@ -106,6 +119,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 5867a8553d53..eafb41470cda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -35,6 +40,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -65,6 +71,10 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -80,6 +90,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
@@ -87,6 +98,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -97,6 +109,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
index e2dcdb0e91b2..6b0c5dd2e1c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +39,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@ public class RestartDialogLayoutTest extends ShellTestCase {
private View mDialogContainer;
private CheckBox mDontRepeatCheckBox;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_restart_dialog_container));
@@ -86,6 +97,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -93,6 +105,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithoutCheckbox() {
mDontRepeatCheckBox.setChecked(false);
assertTrue(mRestartButton.performClick());
@@ -101,6 +114,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithCheckbox() {
mDontRepeatCheckBox.setChecked(true);
assertTrue(mRestartButton.performClick());
@@ -109,6 +123,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClickedDoesntDismiss() {
assertFalse(mLayout.performClick());
@@ -116,6 +131,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -124,6 +140,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
@@ -135,6 +152,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetRestartOnClickListenerNull() {
mLayout.setRestartOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
index 9f109a1d0f50..cfeef90cb4b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -33,6 +38,7 @@ import com.android.wm.shell.transition.Transitions;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
private RestartDialogWindowManager mWindowManager;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -86,6 +97,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 02316125bcc3..3fa21cee0d68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.compatui;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -28,6 +29,9 @@ import android.app.ActivityManager;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -47,6 +51,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
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;
@@ -86,6 +91,10 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
private UserAspectRatioSettingsLayout mLayout;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -107,6 +116,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsButton() {
final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
button.performClick();
@@ -123,6 +133,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForUserAspectRatioButton() {
doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
@@ -133,6 +144,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsHint() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
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 94e168ed70ed..9f288cc4bd93 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
@@ -22,6 +22,7 @@ import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,6 +40,9 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
@@ -61,6 +65,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;
@@ -107,6 +112,10 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -138,6 +147,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateUserAspectRatioButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -178,6 +188,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -190,6 +201,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -242,6 +254,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ false);
@@ -267,6 +280,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -292,6 +306,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -310,6 +325,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -329,6 +345,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -353,6 +370,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.removeLayout();
@@ -378,6 +396,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutHasUserAspectRatioSettingsButton() {
clearInvocations(mWindowManager);
spyOn(mWindowManager);
@@ -411,6 +430,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -419,6 +439,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonClicked() {
mWindowManager.onUserAspectRatioSettingsButtonClicked();
@@ -433,6 +454,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -448,6 +470,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index fb03f20f939c..d4596396840e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -19,6 +19,8 @@ import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
@@ -36,11 +38,12 @@ import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
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 com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
@@ -49,26 +52,27 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.same
+import org.mockito.kotlin.spy
import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
/**
* Test class for {@link DesktopModeLoggerTransitionObserver}
@@ -77,20 +81,17 @@ import org.mockito.kotlin.verifyZeroInteractions
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class DesktopModeLoggerTransitionObserverTest {
+class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
@JvmField
@Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeEventLogger::class.java)
- .mockStatic(DesktopModeStatus::class.java)
- .build()!!
+ ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock private lateinit var mockShellInit: ShellInit
- @Mock private lateinit var transitions: Transitions
- @Mock private lateinit var context: Context
+ private val testExecutor = mock<ShellExecutor>()
+ private val mockShellInit = mock<ShellInit>()
+ private val transitions = mock<Transitions>()
+ private val context = mock<Context>()
private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var shellInit: ShellInit
@@ -98,9 +99,9 @@ class DesktopModeLoggerTransitionObserverTest {
@Before
fun setup() {
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- shellInit = Mockito.spy(ShellInit(testExecutor))
- desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ shellInit = spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock<DesktopModeEventLogger>()
transitionObserver =
DesktopModeLoggerTransitionObserver(
@@ -121,7 +122,7 @@ class DesktopModeLoggerTransitionObserverTest {
@Test
fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
@@ -132,22 +133,17 @@ class DesktopModeLoggerTransitionObserverTest {
@Test
fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FREEFORM_INTENT))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
// task change is finalised when drag ends
val transitionInfo =
TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
@@ -155,82 +151,57 @@ class DesktopModeLoggerTransitionObserverTest {
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_DRAG))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_MENU_BUTTON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.KEYBOARD_SHORTCUT_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE)
}
@Test
fun transitToFront_logTaskAddedAndEnterReasonOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -238,35 +209,29 @@ class DesktopModeLoggerTransitionObserverTest {
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_TO_FRONT
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -274,35 +239,29 @@ class DesktopModeLoggerTransitionObserverTest {
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_CHANGE
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -310,35 +269,29 @@ class DesktopModeLoggerTransitionObserverTest {
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_OPEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -349,286 +302,389 @@ class DesktopModeLoggerTransitionObserverTest {
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
- .build()
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
- .addChange(change)
- .build()
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
+ .addChange(change)
+ .build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE)
}
@Test
fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.SCREEN_ON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
}
@Test
- fun transitSleep_logTaskAddedAndExitReasonScreenOff_sessionIdNull() {
+ fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.SCREEN_OFF))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.DRAG_TO_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.KEYBOARD_SHORTCUT_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.UNKNOWN_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// recents transition
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// task closing
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.TASK_FINISHED))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE)
}
@Test
fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
val sessionId = 1
// add a freeform task to an existing session
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
transitionObserver.setLoggerSessionId(sessionId)
// recents transition sent freeform window to back
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_BACK, taskInfo)
val transitionInfo1 =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
callOnTransitionReady(transitionInfo1)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
callOnTransitionReady(transitionInfo2)
- verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
- verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
val sessionId = 1
// add an existing freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// new freeform task added
- val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
}
@Test
+ fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() {
+ val sessionId = 1
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task position changed
+ val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskResized_logsTaskUpdate() {
+ val sessionId = 1
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task resized
+ val newTaskInfo =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(sessionId),
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() {
+ val sessionId = 1
+ // add 2 existing freeform task
+ val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo1)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task 1 position update
+ val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1))
+ .build()
+ callOnTransitionReady(transitionInfo1)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+ verifyZeroInteractions(desktopModeEventLogger)
+
+ // task 2 resize
+ val newTaskInfo2 =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ id = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100)
+ val transitionInfo2 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2))
+ .build()
+
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(sessionId),
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
val sessionId = 1
// add two existing freeform tasks
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
transitionObserver.setLoggerSessionId(sessionId)
- // new freeform task added
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ // new freeform task closed
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
}
/** Simulate calling the onTransitionReady() method */
private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
- val transition = mock(IBinder::class.java)
- val startT = mock(SurfaceControl.Transaction::class.java)
- val finishT = mock(SurfaceControl.Transaction::class.java)
+ val transition = mock<IBinder>()
+ val startT = mock<SurfaceControl.Transaction>()
+ val finishT = mock<SurfaceControl.Transaction>()
transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
}
- companion object {
- fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
- val taskInfo = ActivityManager.RunningTaskInfo()
- taskInfo.taskId = taskId
- taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+ private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) {
+ val sessionId = transitionObserver.getLoggerSessionId()
+ assertNotNull(sessionId)
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
- return taskInfo
- }
+ private fun verifyTaskRemovedAndExitLogging(
+ sessionId: Int,
+ exitReason: ExitReason,
+ taskUpdate: TaskUpdate
+ ) {
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate))
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason))
+ verifyZeroInteractions(desktopModeEventLogger)
+ assertNull(transitionObserver.getLoggerSessionId())
+ }
+
+ private companion object {
+ const val DEFAULT_TASK_ID = 1
+ const val DEFAULT_TASK_UID = 2
+ const val DEFAULT_TASK_HEIGHT = 100
+ const val DEFAULT_TASK_WIDTH = 200
+ const val DEFAULT_TASK_X = 30
+ const val DEFAULT_TASK_Y = 70
+ val DEFAULT_TASK_UPDATE =
+ TaskUpdate(
+ DEFAULT_TASK_ID,
+ DEFAULT_TASK_UID,
+ DEFAULT_TASK_HEIGHT,
+ DEFAULT_TASK_WIDTH,
+ DEFAULT_TASK_X,
+ DEFAULT_TASK_Y,
+ )
+
+ fun createTaskInfo(
+ windowMode: Int,
+ id: Int = DEFAULT_TASK_ID,
+ uid: Int = DEFAULT_TASK_UID,
+ taskHeight: Int = DEFAULT_TASK_HEIGHT,
+ taskWidth: Int = DEFAULT_TASK_WIDTH,
+ taskX: Int = DEFAULT_TASK_X,
+ taskY: Int = DEFAULT_TASK_Y,
+ ) =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = id
+ userId = uid
+ configuration.windowConfiguration.apply {
+ windowingMode = windowMode
+ positionInParent = Point(taskX, taskY)
+ bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight))
+ }
+ }
fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
val change =
- Change(
- WindowContainerToken(mock(IWindowContainerToken::class.java)),
- mock(SurfaceControl::class.java))
+ Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>())
change.mode = mode
change.taskInfo = taskInfo
return change
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 310ccc252469..6612aee0cd12 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
@@ -119,54 +119,91 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
@Test
- fun isOnlyActiveTask_noActiveTasks() {
- // Not an active task
- assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ fun isOnlyVisibleNonClosingTask_noTasks() {
+ // No visible tasks
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ assertThat(repo.isClosingTask(1)).isFalse()
}
@Test
- fun isOnlyActiveTask_singleActiveTask() {
- repo.addActiveTask(DEFAULT_DISPLAY, 1)
- // The only active task
- assertThat(repo.isActiveTask(1)).isTrue()
- assertThat(repo.isOnlyActiveTask(1)).isTrue()
- // Not an active task
- assertThat(repo.isActiveTask(99)).isFalse()
- assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() {
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+
+ // The only visible task
+ assertThat(repo.isVisibleTask(1)).isTrue()
+ assertThat(repo.isClosingTask(1)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue()
+ // Not a visible task
+ assertThat(repo.isVisibleTask(99)).isFalse()
+ assertThat(repo.isClosingTask(99)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
+ }
+
+ @Test
+ fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() {
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.addClosingTask(DEFAULT_DISPLAY, 1)
+
+ // A visible task that's closing
+ assertThat(repo.isVisibleTask(1)).isTrue()
+ assertThat(repo.isClosingTask(1)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ // Not a visible task
+ assertThat(repo.isVisibleTask(99)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
+ }
+
+ @Test
+ fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() {
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.minimizeTask(DEFAULT_DISPLAY, 1)
+
+ // The visible task that's closing
+ assertThat(repo.isVisibleTask(1)).isTrue()
+ assertThat(repo.isMinimizedTask(1)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ // Not a visible task
+ assertThat(repo.isVisibleTask(99)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
}
@Test
- fun isOnlyActiveTask_multipleActiveTasks() {
- repo.addActiveTask(DEFAULT_DISPLAY, 1)
- repo.addActiveTask(DEFAULT_DISPLAY, 2)
+ fun isOnlyVisibleNonClosingTask_multipleVisibleNonClosingTasks() {
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+
// Not the only task
- assertThat(repo.isActiveTask(1)).isTrue()
- assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ assertThat(repo.isVisibleTask(1)).isTrue()
+ assertThat(repo.isClosingTask(1)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
// Not the only task
- assertThat(repo.isActiveTask(2)).isTrue()
- assertThat(repo.isOnlyActiveTask(2)).isFalse()
- // Not an active task
- assertThat(repo.isActiveTask(99)).isFalse()
- assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ assertThat(repo.isVisibleTask(2)).isTrue()
+ assertThat(repo.isClosingTask(2)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse()
+ // Not a visible task
+ assertThat(repo.isVisibleTask(99)).isFalse()
+ assertThat(repo.isClosingTask(99)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
}
@Test
- fun isOnlyActiveTask_multipleDisplays() {
- repo.addActiveTask(DEFAULT_DISPLAY, 1)
- repo.addActiveTask(DEFAULT_DISPLAY, 2)
- repo.addActiveTask(SECOND_DISPLAY, 3)
+ fun isOnlyVisibleNonClosingTask_multipleDisplays() {
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 3, visible = true)
+
// Not the only task on DEFAULT_DISPLAY
- assertThat(repo.isActiveTask(1)).isTrue()
- assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ assertThat(repo.isVisibleTask(1)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
// Not the only task on DEFAULT_DISPLAY
- assertThat(repo.isActiveTask(2)).isTrue()
- assertThat(repo.isOnlyActiveTask(2)).isFalse()
- // The only active task on SECOND_DISPLAY
- assertThat(repo.isActiveTask(3)).isTrue()
- assertThat(repo.isOnlyActiveTask(3)).isTrue()
- // Not an active task
- assertThat(repo.isActiveTask(99)).isFalse()
- assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ assertThat(repo.isVisibleTask(2)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse()
+ // The only visible task on SECOND_DISPLAY
+ assertThat(repo.isVisibleTask(3)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue()
+ // Not a visible task
+ assertThat(repo.isVisibleTask(99)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
}
@Test
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 e17f7f2f7b12..6cabbf90bbc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,7 +18,9 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -45,6 +47,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -87,7 +90,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -102,6 +105,8 @@ import com.google.common.truth.Truth.assertWithMessage
import java.util.Optional
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -133,6 +138,7 @@ import org.mockito.quality.Strictness
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
@@ -146,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
+ @Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock
@@ -188,7 +195,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
shellInit = spy(ShellInit(testExecutor))
@@ -230,6 +236,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
rootTaskDisplayAreaOrganizer,
dragAndDropController,
transitions,
+ keyguardManager,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
@@ -257,8 +264,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
+ fun instantiate_canNotEnterDesktopMode_doNotAddInitCallback() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
createController()
@@ -436,14 +443,15 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun showDesktopApps_dontReorderMinimizedTask() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
+
markTaskHidden(freeformTask)
markTaskHidden(minimizedTask)
desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
-
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
@@ -454,6 +462,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Reorder freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ }
+
+ @Test
fun getVisibleTaskCount_noTasks_returnsZero() {
assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@@ -642,33 +670,68 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun moveToDesktop_nonRunningTask_launchesInFreeform() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
+ val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- val task = createTaskInfo(1)
+ controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
with(getLatestEnterDesktopWct()) {
- assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ // Add desktop wallpaper activity
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Launch task
+ assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
}
}
@Test
- fun moveToDesktop_topActivityTranslucent_doesNothing() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
val task =
- setUpFullscreenTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
- }
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
+
+ controller.moveToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+ val task =
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
+ numActivities = 1
+ }
controller.moveToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun moveToDesktop_systemUIActivity_doesNothing() {
val task = setUpFullscreenTask()
@@ -773,21 +836,44 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val homeTask = setUpHomeTask()
val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
wct.assertReorderAt(0, homeTask)
- for (i in 1..<taskLimit) { // Skipping freeformTasks[0]
- wct.assertReorderAt(index = i, task = freeformTasks[i])
- }
- wct.assertReorderAt(taskLimit, newTask)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(taskLimit + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
+ val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ setUpHomeTask()
+
+ controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(taskLimit + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask
+ )
}
@Test
@@ -923,7 +1009,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun onDesktopWindowClose_noActiveTasks() {
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, 1 /* taskId */)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = 1)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -932,7 +1018,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
val task = setUpFreeformTask()
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -944,12 +1030,38 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
// Adds remove wallpaper operation
wct.assertRemoveAt(index = 0, wallpaperToken)
}
@Test
+ fun onDesktopWindowClose_singleActiveTask_isClosing() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_isMinimized() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun onDesktopWindowClose_multipleActiveTasks() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -957,22 +1069,56 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task1.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@Test
+ fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val fullscreenTask = createFullscreenTask()
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ assertThat(wct.hierarchyOps).hasSize(2)
+ wct.assertReorderAt(1, homeTask, toTop = false)
}
@Test
@@ -1052,41 +1198,106 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ result.assertReorderAt(1, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
val result =
- controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
+ controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
- assertThat(result?.hierarchyOps?.size).isEqualTo(2)
- result!!.assertReorderAt(1, freeformTask2, toTop = true)
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(3)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring active desktop tasks to front
+ result.assertReorderAt(1, freeformTask1, toTop = true)
+ // Bring new task to front
+ result.assertReorderAt(2, freeformTask2, toTop = true)
}
@Test
- fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, task, toTop = true)
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, task, toTop = true)
}
@Test
- fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, task, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
}
@Test
@@ -1118,6 +1329,17 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun handleRequest_freeformTask_keyguardLocked_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(result, "Should NOT handle request")
+ }
+
+ @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1171,20 +1393,40 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
val task =
- setUpFreeformTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
- }
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+ val task =
+ setUpFreeformTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
val task = setUpFreeformTask()
@@ -1200,48 +1442,205 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_noToken() {
+ fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
val task = setUpFreeformTask()
+
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
+
+ assertNull(result, "Should not handle request")
}
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() {
+ fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
+
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() {
+ fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
+
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
val task = setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertThat(result).isNotNull()
- // Creates remove wallpaper transaction
- result!!.assertRemoveAt(index = 0, wallpaperToken)
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasks() {
+ fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request")
+ // Should create remove wallpaper transaction
+ .assertRemoveAt(index = 0, wallpaperToken)
}
@Test
@@ -1562,6 +1961,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+ val bounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo = ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = false
+ }
+
+ // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+ val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+ controller.toggleDesktopTaskSize(task)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ }
+
+ @Test
fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
@@ -1588,6 +2007,46 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
+ boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
+ STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
val boundsBeforeMaximize = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
@@ -1615,6 +2074,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
task.topActivityInfo = activityInfo
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(displayId, task.taskId, visible = true)
desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
runningTasks.add(task)
return task
@@ -1826,6 +2286,16 @@ private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: Runni
}
}
+/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */
+private fun WindowContainerTransaction.assertReorderSequenceInRange(
+ range: IntRange,
+ vararg tasks: RunningTaskInfo
+) {
+ assertThat(hierarchyOps.slice(range).map { it.type to it.container })
+ .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
+ .inOrder()
+}
+
private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
assertIndexInBounds(index)
val op = hierarchyOps[index]
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 77f917cc28d8..8d9fd9166050 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -24,6 +24,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -32,7 +33,7 @@ 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.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
@@ -205,6 +206,46 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+ desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
+ desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[0].container).isEqualTo(task1.token.asBinder())
+ assertThat(wct.hierarchyOps[1].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[1].container).isEqualTo(task2.token.asBinder())
+ }
+
+ @Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
(1..<taskLimit).forEach { _ -> setUpFreeformTask() }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index b2467e9a62cf..e5157c974e2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -45,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -65,6 +66,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
@Mock
private Transitions mTransitions;
@Mock
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
IBinder mToken;
@Mock
Supplier<SurfaceControl.Transaction> mTransactionFactory;
@@ -94,7 +97,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext);
+ mContext, mInteractionJankMonitor);
mPoint = new Point(0, 0);
}
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 a64ebd301c00..840126421c08 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
@@ -76,6 +76,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
private DisplayController mDisplayController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -96,8 +98,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
- mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
- mGlobalDragListener, mTransitions, mMainExecutor);
+ mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger,
+ mIconProvider, 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 6e72e8df8d62..97fa8d6ceca9 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
@@ -65,8 +65,6 @@ import android.content.res.Resources;
import android.graphics.Insets;
import android.os.RemoteException;
import android.view.DisplayInfo;
-import android.view.DragEvent;
-import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -76,7 +74,6 @@ 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;
@@ -106,6 +103,8 @@ public class DragAndDropPolicyTest extends ShellTestCase {
// Both the split-screen and start interface.
@Mock
private SplitScreenController mSplitScreenStarter;
+ @Mock
+ private DragAndDropPolicy.Starter mFullscreenStarter;
@Mock
private InstanceId mLoggerSessionId;
@@ -151,7 +150,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
mInsets = Insets.of(0, 0, 0, 0);
- mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
+ mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter));
mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
mLaunchableIntentPendingIntent = mock(PendingIntent.class);
when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
@@ -285,14 +284,14 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
- verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_UNDEFINED), any());
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
+ verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
+ eq(SPLIT_POSITION_UNDEFINED), any(), any());
}
private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
@@ -300,19 +299,19 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
@@ -320,19 +319,20 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mPortraitDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
+ null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
@Test
@@ -340,7 +340,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3f3cafcf6375..6ec6bed860bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -36,7 +36,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 5880ffb0dce2..72950a8dc139 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -88,8 +88,11 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue1 = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
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 d38fc6cb6418..75d21457b60b 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
@@ -34,7 +34,6 @@ import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -183,7 +182,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
}
@Test
@@ -235,27 +234,6 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
- public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component1);
-
- verify(mMockPipBoundsState).setLastPipComponentName(null);
- }
-
- @Test
- public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- final ComponentName component2 = new ComponentName(mContext, "component2");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component2);
-
- verify(mMockPipBoundsState, never()).setLastPipComponentName(null);
- }
-
- @Test
public void saveReentryState_savesPipBoundsState() {
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
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 ace09a82d71c..66f8c0b9558d 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
@@ -114,8 +114,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
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 92762fa68550..6d18e3696f84 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
@@ -116,8 +116,8 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 974539f23b80..aa2d6f09508f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -241,16 +241,16 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_valid() {
- mTvPipBoundsState.setTvPipExpanded(true);
-
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true);
moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true);
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true);
@@ -281,10 +281,9 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_invalid() {
- mTvPipBoundsState.setTvPipExpanded(true);
-
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
@@ -297,6 +296,7 @@ public class TvPipGravityTest extends ShellTestCase {
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index bbd65be9abda..15b73c541ed8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.recents
import android.app.ActivityManager
+import android.app.ActivityManager.RecentTaskInfo
import android.graphics.Rect
import android.os.Parcel
import android.testing.AndroidTestingRunner
@@ -33,6 +34,7 @@ import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -86,12 +88,13 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
@Test
fun testFreeformTasks_hasCorrectType() {
- assertThat(freeformTasksGroupInfo().type).isEqualTo(TYPE_FREEFORM)
+ assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).type)
+ .isEqualTo(TYPE_FREEFORM)
}
@Test
- fun testSplitTasks_taskInfoList_hasThreeTasks() {
- val list = freeformTasksGroupInfo().taskInfoList
+ fun testCreateFreeformTasks_hasCorrectNumberOfTasks() {
+ val list = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3)).taskInfoList
assertThat(list).hasSize(3)
assertThat(list[0].taskId).isEqualTo(1)
assertThat(list[1].taskId).isEqualTo(2)
@@ -99,6 +102,16 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
}
@Test
+ fun testCreateFreeformTasks_nonExistentMinimizedTaskId_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ freeformTasksGroupInfo(
+ freeformTaskIds = arrayOf(1, 2, 3),
+ minimizedTaskIds = arrayOf(1, 4)
+ )
+ }
+ }
+
+ @Test
fun testParcelling_singleTask() {
val recentTaskInfo = singleTaskGroupInfo()
val parcel = Parcel.obtain()
@@ -129,7 +142,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
@Test
fun testParcelling_freeformTasks() {
- val recentTaskInfo = freeformTasksGroupInfo()
+ val recentTaskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3))
val parcel = Parcel.obtain()
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
@@ -145,6 +158,21 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
.containsExactly(1, 2, 3)
}
+ @Test
+ fun testParcelling_freeformTasks_minimizedTasks() {
+ val recentTaskInfo = freeformTasksGroupInfo(
+ freeformTaskIds = arrayOf(1, 2, 3), minimizedTaskIds = arrayOf(2))
+
+ val parcel = Parcel.obtain()
+ recentTaskInfo.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ // Read the object back from the parcel
+ val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
+ assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
+ }
+
private fun createTaskInfo(id: Int) = ActivityManager.RecentTaskInfo().apply {
taskId = id
token = WindowContainerToken(mock(IWindowContainerToken::class.java))
@@ -162,10 +190,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
- private fun freeformTasksGroupInfo(): GroupedRecentTaskInfo {
- val task1 = createTaskInfo(id = 1)
- val task2 = createTaskInfo(id = 2)
- val task3 = createTaskInfo(id = 3)
- return GroupedRecentTaskInfo.forFreeformTasks(task1, task2, task3)
+ private fun freeformTasksGroupInfo(
+ freeformTaskIds: Array<Int>,
+ minimizedTaskIds: Array<Int> = emptyArray()
+ ): GroupedRecentTaskInfo {
+ return GroupedRecentTaskInfo.forFreeformTasks(
+ freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(),
+ minimizedTaskIds.toSet())
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e291c0e1a151..a0aab2e6c8a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,7 +68,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -399,7 +399,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
- public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() {
+ public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
@@ -415,8 +415,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents
- // entries
+ // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
@@ -428,9 +427,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
// Check freeform group entries
- assertEquals(2, freeformGroup.getTaskInfoList().size());
+ assertEquals(3, freeformGroup.getTaskInfoList().size());
assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
- assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+ assertEquals(t3, freeformGroup.getTaskInfoList().get(1));
+ assertEquals(t5, freeformGroup.getTaskInfoList().get(2));
// Check single entries
assertEquals(t2, singleGroup1.getTaskInfo1());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index f9599702e763..0e5efa650cc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -48,7 +48,6 @@ import org.mockito.kotlin.same
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-
/**
* Test class for {@link TaskStackTransitionObserver}
*
@@ -168,6 +167,80 @@ class TaskStackTransitionObserverTest {
.isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withChange_onlyOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_CHANGE,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(change.taskInfo?.windowingMode)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withOpen_lastOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(mergedChange.taskInfo?.windowingMode)
+ }
+
class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
@@ -179,11 +252,14 @@ class TaskStackTransitionObserverTest {
}
/** Simulate calling the onTransitionReady() method */
- private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ private fun callOnTransitionReady(
+ transitionInfo: TransitionInfo,
+ transition: IBinder = mockTransitionBinder
+ ) {
val startT = Mockito.mock(SurfaceControl.Transaction::class.java)
val finishT = Mockito.mock(SurfaceControl.Transaction::class.java)
- transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT)
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
}
/** Simulate calling the onTransitionFinished() method */
@@ -191,6 +267,11 @@ class TaskStackTransitionObserverTest {
transitionObserver.onTransitionFinished(mockTransitionBinder, false)
}
+ /** Simulate calling the onTransitionMerged() method */
+ private fun callOnTransitionMerged(merged: IBinder, playing: IBinder = mockTransitionBinder) {
+ transitionObserver.onTransitionMerged(merged, playing)
+ }
+
companion object {
fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo {
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
new file mode 100644
index 000000000000..b1d62f485a2a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -0,0 +1,456 @@
+/*
+ * 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.shared.desktopmode
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.Companion.convertToToggleOverrideWithFallback
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test class for [DesktopModeFlags]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeFlagsTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeFlagsTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Before
+ fun setUp() {
+ resetCache()
+ }
+
+ // TODO(b/348193756): Add tests
+ // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and
+ // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non
+ // overridable flags to DesktopModeFlags.
+
+ @Test
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // In absence of dev options, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // In absence of dev options, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For overridableFlag, for unset overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For overridableFlag, for unset overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noOverride_featureFlagOn_returnsTrue() {
+ setOverride(null)
+
+ // For overridableFlag, in absence of overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noOverride_featureFlagOff_returnsFalse() {
+ setOverride(null)
+
+ // For overridableFlag, in absence of overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() {
+ setOverride(-2)
+
+ // For overridableFlag, for recognizable overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() {
+ setOverride(-2)
+
+ // For overridableFlag, for recognizable overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOff_featureFlagOn_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOn_featureFlagOff_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+
+ setOverride(OVERRIDE_ON.setting)
+
+ // Keep overrides constant through the process
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Keep overrides constant through the process
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_ON.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_ON.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_UNSET.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_UNSET.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc")
+ setOverride(OVERRIDE_OFF.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if currently invalid
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2")
+ setOverride(OVERRIDE_OFF.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if currently invalid
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString())
+ setOverride(OVERRIDE_ON.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString())
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_ON.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString())
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOff_returnFalse() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOn_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOff_returnTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ fun convertToToggleOverrideWithFallback_validInt_returnsToggleOverride() {
+ assertThat(convertToToggleOverrideWithFallback(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF)
+ assertThat(convertToToggleOverrideWithFallback(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET)
+ }
+
+ @Test
+ fun convertToToggleOverrideWithFallback_invalidInt_returnsFallback() {
+ assertThat(convertToToggleOverrideWithFallback(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET)
+ }
+
+ private fun setOverride(setting: Int?) {
+ val contentResolver = mContext.contentResolver
+ val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+ if (setting == null) {
+ Settings.Global.putString(contentResolver, key, null)
+ } else {
+ Settings.Global.putInt(contentResolver, key, setting)
+ }
+ }
+
+ private fun resetCache() {
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, null)
+
+ // Clear override cache stored in System property
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ }
+
+ private companion object {
+ const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 000000000000..2fabd4a33586
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS \ No newline at end of file
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 3c387f0d7c34..5b95b1588814 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
@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -49,6 +50,9 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
+import android.os.IBinder;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -195,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@@ -213,19 +217,20 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -233,15 +238,16 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
- doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@@ -249,7 +255,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -261,13 +267,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
// Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
- .findTaskInBackground(any(), anyInt());
+ .findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
}
@Test
@@ -284,7 +290,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
@@ -312,6 +318,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
info.supportsMultiWindow = true;
info.baseIntent = strIntent;
info.baseActivity = strIntent.getComponent();
+ info.token = new WindowContainerToken(mock(IWindowContainerToken.class));
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java
new file mode 100644
index 000000000000..b54c3bf72110
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.transition;
+
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+
+public class ChangeBuilder {
+ final TransitionInfo.Change mChange;
+
+ ChangeBuilder(@WindowManager.TransitionType int mode) {
+ mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
+ mChange.setMode(mode);
+ }
+
+ ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
+ mChange.setFlags(flags);
+ return this;
+ }
+
+ ChangeBuilder setTask(RunningTaskInfo taskInfo) {
+ mChange.setTaskInfo(taskInfo);
+ return this;
+ }
+
+ ChangeBuilder setRotate(int anim) {
+ return setRotate(Surface.ROTATION_90, anim);
+ }
+
+ ChangeBuilder setRotate() {
+ return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
+ }
+
+ ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
+ mChange.setRotation(Surface.ROTATION_0, target);
+ mChange.setRotationAnimation(anim);
+ return this;
+ }
+
+ TransitionInfo.Change build() {
+ return mChange;
+ }
+
+ private static SurfaceControl createMockSurface(boolean valid) {
+ SurfaceControl sc = mock(SurfaceControl.class);
+ doReturn(valid).when(sc).isValid();
+ return sc;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
new file mode 100644
index 000000000000..754a173ff069
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_SYNC;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the default animation handler that is used if no other special-purpose handler picks
+ * up an animation request.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultTransitionHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultTransitionHandlerTest extends ShellTestCase {
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private final DisplayController mDisplayController = mock(DisplayController.class);
+ private final TransactionPool mTransactionPool = new MockTransactionPool();
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final TestShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+ private ShellInit mShellInit;
+ private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private DefaultTransitionHandler mTransitionHandler;
+
+ @Before
+ public void setUp() {
+ mShellInit = new ShellInit(mMainExecutor);
+ mRootTaskDisplayAreaOrganizer = new RootTaskDisplayAreaOrganizer(
+ mMainExecutor,
+ mContext,
+ mShellInit);
+ mTransitionHandler = new DefaultTransitionHandler(
+ mContext, mShellInit, mDisplayController,
+ mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
+ mRootTaskDisplayAreaOrganizer);
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ flushHandlers();
+ }
+
+ private void flushHandlers() {
+ mMainHandler.runWithScissors(() -> {
+ mAnimExecutor.flushAll();
+ mMainExecutor.flushAll();
+ }, 1000L);
+ }
+
+ @Test
+ public void testAnimationBackgroundCreatedForTaskTransition() {
+ final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(1))
+ .build();
+ final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(2))
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openTask)
+ .addChange(closeTask)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT).setColor(any(), any());
+ }
+
+ @Test
+ public void testNoAnimationBackgroundForTranslucentTasks() {
+ final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(1))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build();
+ final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(2))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openTask)
+ .addChange(closeTask)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT, never()).setColor(any(), any());
+ }
+
+ @Test
+ public void testNoAnimationBackgroundForWallpapers() {
+ final TransitionInfo.Change openWallpaper = new ChangeBuilder(TRANSIT_OPEN)
+ .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
+ .build();
+ final TransitionInfo.Change closeWallpaper = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openWallpaper)
+ .addChange(closeWallpaper)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT, never()).setColor(any(), any());
+ }
+
+ private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
+ handler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
+ MockTransactionPool.create(),
+ token,
+ mock(Transitions.TransitionFinishCallback.class));
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
+ taskInfo.token = mock(WindowContainerToken.class);
+ return taskInfo;
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
new file mode 100644
index 000000000000..574a87ac4b17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
@@ -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.transition;
+
+import static org.mockito.Mockito.RETURNS_SELF;
+import static org.mockito.Mockito.mock;
+
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.util.StubTransaction;
+
+public class MockTransactionPool extends TransactionPool {
+
+ public static SurfaceControl.Transaction create() {
+ return mock(StubTransaction.class, RETURNS_SELF);
+ }
+
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return create();
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+}
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 964d86e8bd35..409b87723e79 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
@@ -79,7 +79,6 @@ import android.util.Pair;
import android.view.IRecentsAnimationRunner;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.IWindowContainerToken;
@@ -1614,43 +1613,6 @@ public class ShellTransitionTests extends ShellTestCase {
eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean());
}
- class ChangeBuilder {
- final TransitionInfo.Change mChange;
-
- ChangeBuilder(@WindowManager.TransitionType int mode) {
- mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
- mChange.setMode(mode);
- }
-
- ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
- mChange.setFlags(flags);
- return this;
- }
-
- ChangeBuilder setTask(RunningTaskInfo taskInfo) {
- mChange.setTaskInfo(taskInfo);
- return this;
- }
-
- ChangeBuilder setRotate(int anim) {
- return setRotate(Surface.ROTATION_90, anim);
- }
-
- ChangeBuilder setRotate() {
- return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
- }
-
- ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
- mChange.setRotation(Surface.ROTATION_0, target);
- mChange.setRotationAnimation(anim);
- return this;
- }
-
- TransitionInfo.Change build() {
- return mChange;
- }
- }
-
class TestTransitionHandler implements Transitions.TransitionHandler {
ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes =
new ArrayList<>();
@@ -1739,12 +1701,6 @@ public class ShellTransitionTests extends ShellTestCase {
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
}
- private static SurfaceControl createMockSurface(boolean valid) {
- SurfaceControl sc = mock(SurfaceControl.class);
- doReturn(valid).when(sc).isValid();
- return sc;
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
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 ca1e3f173e24..b1803e97b107 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
@@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -67,14 +68,16 @@ 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.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import java.util.Optional
import java.util.function.Supplier
import org.junit.Assert.assertEquals
@@ -82,6 +85,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -131,6 +135,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -164,7 +169,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockInputMonitorFactory,
transactionFactory,
mockRootTaskDisplayAreaOrganizer,
- windowDecorByTaskIdSpy
+ windowDecorByTaskIdSpy, mockInteractionJankMonitor
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -343,10 +348,35 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testDecorationIsNotCreatedForTopTranslucentActivities() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
numActivities = 1
}
onTaskOpening(task)
@@ -356,6 +386,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForSystemUIActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
@@ -518,6 +549,99 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
}
+ @Test
+ fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
+ }
+
+ @Test
+ fun testOnDecorMaximizedOrRestored_closesMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testOnDecorSnappedLeft_snapResizes() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnLeftSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT)
+ }
+
+ @Test
+ fun testOnDecorSnappedLeft_closeMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnLeftSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testOnDecorSnappedRight_snapResizes() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnRightSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT)
+ }
+
+ @Test
+ fun testOnDecorSnappedRight_closeMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnRightSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
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 46c158908226..d8606093ac5c 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
@@ -24,9 +24,15 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
+import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -38,11 +44,14 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
@@ -62,6 +71,7 @@ import android.view.View;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -74,8 +84,12 @@ 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.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
import org.junit.After;
import org.junit.Before;
@@ -84,6 +98,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.quality.Strictness;
@@ -105,6 +120,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
+ private static final Uri TEST_URI = Uri.parse("www.google.com");
+
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
@@ -112,8 +129,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private Handler mMockHandler;
- @Mock
private Choreographer mMockChoreographer;
@Mock
private SyncTransactionQueue mMockSyncQueue;
@@ -131,13 +146,20 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
-
@Mock
private TestTouchEventListener mMockTouchEventListener;
@Mock
private DesktopModeWindowDecoration.ExclusionRegionListener mMockExclusionRegionListener;
@Mock
private PackageManager mMockPackageManager;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ @Captor
+ private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
+ @Captor
+ private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable;
private final InsetsState mInsetsState = new InsetsState();
private SurfaceControl.Transaction mMockTransaction;
@@ -154,7 +176,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Before
- public void setUp() {
+ public void setUp() throws PackageManager.NameNotFoundException {
mMockitoSession = mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus.class)
@@ -170,6 +192,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mTestableContext.ensureTestableResources();
mContext.setMockPackageManager(mMockPackageManager);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
+ final ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo);
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
@@ -459,6 +484,151 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
}
+ @Test
+ public void createMaximizeMenu_showsMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ assertFalse(decoration.isMaximizeMenuActive());
+
+ createMaximizeMenu(decoration, menu);
+
+ assertTrue(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_unHoversMenu_schedulesCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+ createMaximizeMenu(decoration, menu);
+
+ mOnMaxMenuHoverChangeListener.getValue().invoke(false);
+
+ verify(mMockHandler)
+ .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
+
+ mCloseMaxMenuRunnable.getValue().run();
+ verify(menu).close();
+ assertFalse(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_unHoversButton_schedulesCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+ createMaximizeMenu(decoration, menu);
+
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+
+ verify(mMockHandler)
+ .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
+
+ mCloseMaxMenuRunnable.getValue().run();
+ verify(menu).close();
+ assertFalse(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_hoversMenu_cancelsCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ createMaximizeMenu(decoration, menu);
+
+ mOnMaxMenuHoverChangeListener.getValue().invoke(true);
+
+ verify(mMockHandler).removeCallbacks(any());
+ }
+
+ @Test
+ public void maximizeMenu_hoversButton_cancelsCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ createMaximizeMenu(decoration, menu);
+
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+
+ verify(mMockHandler).removeCallbacks(any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ // Asset runnable posted to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkNotResetToSameLink() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+ // Set captured link and run on captured link expired runnable
+ decor.relayout(taskInfo);
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ decor.relayout(taskInfo);
+ // Assert captured link not set to same value twice
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkExpiresAfterClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ decor.onOpenInBrowserClick();
+ //Assert Captured link expires after button is clicked
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_openInBrowserListenerCalledOnClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ decor.onOpenInBrowserClick();
+
+ verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ }
+
+ private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
+ final OnTaskActionClickListener l = (taskId, tag) -> {};
+ decoration.setOnMaximizeOrRestoreClickListener(l);
+ decoration.setOnLeftSnapClickListener(l);
+ decoration.setOnRightSnapClickListener(l);
+ decoration.createMaximizeMenu();
+ verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture());
+ }
+
private void fillRoundedCornersResources(int fillValue) {
when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
.thenReturn(fillValue);
@@ -479,15 +649,22 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory());
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ MaximizeMenuFactory maximizeMenuFactory) {
+ final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
+ windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
return windowDecor;
}
@@ -499,12 +676,12 @@ 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",
"DesktopModeWindowDecorationTests");
+ taskInfo.capturedLink = TEST_URI;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
@@ -541,4 +718,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
return false;
}
}
+
+ private static final class FakeMaximizeMenuFactory implements MaximizeMenuFactory {
+ private final MaximizeMenu mMaximizeMenu;
+
+ FakeMaximizeMenuFactory() {
+ this(mock(MaximizeMenu.class));
+ }
+
+ FakeMaximizeMenuFactory(MaximizeMenu menu) {
+ mMaximizeMenu = menu;
+ }
+
+ @NonNull
+ @Override
+ public MaximizeMenu create(@NonNull SyncTransactionQueue syncQueue,
+ @NonNull RootTaskDisplayAreaOrganizer rootTdaOrganizer,
+ @NonNull DisplayController displayController,
+ @NonNull ActivityManager.RunningTaskInfo taskInfo,
+ @NonNull Context decorWindowContext, @NonNull PointF menuPosition,
+ @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ return mMaximizeMenu;
+ }
+ }
}
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 f750e6b9a6fe..e52971120478 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
@@ -21,27 +21,35 @@ import android.content.res.Resources
import android.graphics.PointF
import android.graphics.Rect
import android.os.IBinder
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display
import android.window.WindowContainerToken
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
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.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.quality.Strictness
/**
* Tests for [DragPositioningCallbackUtility].
@@ -53,24 +61,39 @@ import org.mockito.MockitoAnnotations
class DragPositioningCallbackUtilityTest {
@Mock
private lateinit var mockWindowDecoration: WindowDecoration<*>
+
@Mock
private lateinit var taskToken: WindowContainerToken
+
@Mock
private lateinit var taskBinder: IBinder
+
@Mock
private lateinit var mockDisplayController: DisplayController
+
@Mock
private lateinit var mockDisplayLayout: DisplayLayout
+
@Mock
private lateinit var mockDisplay: Display
+
@Mock
private lateinit var mockContext: Context
+
@Mock
private lateinit var mockResources: Resources
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
+
+ private lateinit var mockitoSession: StaticMockitoSession
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java).startMocking()
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
@@ -91,6 +114,11 @@ class DragPositioningCallbackUtilityTest {
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
@Test
fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() {
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
@@ -238,7 +266,7 @@ class DragPositioningCallbackUtilityTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() {
- whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+ doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
val startingPoint =
PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
@@ -261,7 +289,7 @@ class DragPositioningCallbackUtilityTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() {
- whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+ doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
val startingPoint =
PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
@@ -323,6 +351,49 @@ class DragPositioningCallbackUtilityTest {
assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50)
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testChangeBounds_windowSizeExceedsStableBounds_shouldBeAllowedToChangeBounds() {
+ val startingPoint =
+ PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
+ OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
+ // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
+ // the disallowed drag area.
+ val offset = 5
+ val newX = STABLE_BOUNDS.right.toFloat() - offset
+ val newY = STABLE_BOUNDS.bottom.toFloat() - offset
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+ assertThat(repositionTaskBounds.width()).isGreaterThan(STABLE_BOUNDS.right)
+ assertThat(repositionTaskBounds.height()).isGreaterThan(STABLE_BOUNDS.bottom)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testChangeBoundsInDesktopMode_windowSizeExceedsStableBounds_shouldBeLimitedToDisplaySize() {
+ doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
+ val startingPoint =
+ PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
+ OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
+ // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
+ // the disallowed drag area.
+ val offset = 5
+ val newX = STABLE_BOUNDS.right.toFloat() - offset
+ val newY = STABLE_BOUNDS.bottom.toFloat() - offset
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+ assertThat(repositionTaskBounds.width()).isLessThan(STABLE_BOUNDS.right)
+ assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom)
+ }
+
private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
@@ -347,6 +418,7 @@ class DragPositioningCallbackUtilityTest {
private const val NAVBAR_HEIGHT = 50
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val OFF_CENTER_STARTING_BOUNDS = Rect(-100, -100, 10, 10)
private val DISALLOWED_RESIZE_AREA = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 4dea5a75a0e8..6a94cd8aa283 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -55,8 +55,6 @@ public class DragResizeWindowGeometryTests {
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
- private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
- + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -91,14 +89,13 @@ public class DragResizeWindowGeometryTests {
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
+ .addEqualityGroup(
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5))
.testEquals();
}
@@ -124,21 +121,21 @@ public class DragResizeWindowGeometryTests {
private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is still contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
// Vertically along the edge is contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
}
/**
@@ -151,10 +148,7 @@ public class DragResizeWindowGeometryTests {
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
GEOMETRY.union(region);
- // Make sure we're choosing a point outside of any debug region buffer.
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
@@ -168,9 +162,7 @@ public class DragResizeWindowGeometryTests {
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = FINE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 5582e0f46321..0c50ab6b5008 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -196,9 +196,9 @@ class HandleMenuTest : ShellTestCase() {
R.layout.desktop_mode_app_header
}
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, true /* shouldShowWindowingPill */,
- 50 /* captionHeight */ )
+ onClickListener, onTouchListener, appIcon, appName, displayController,
+ splitScreenController, true /* shouldShowWindowingPill */,
+ true /* shouldShowBrowserPill */, 50 /* captionHeight */)
handleMenu.show()
return handleMenu
}
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 48ac1e5717aa..943c313e5b40 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
@@ -17,6 +17,8 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager
import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
import android.os.IBinder
@@ -31,6 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
@@ -98,6 +101,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
private lateinit var mockFinishCallback: TransitionFinishCallback
@Mock
private lateinit var mockTransitions: Transitions
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var mockResources: Resources
+ @Mock
+ private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -105,6 +114,9 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockContext.getResources()).thenReturn(mockResources)
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -141,7 +153,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions
+ mockTransitions,
+ mockInteractionJankMonitor
)
}
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 f3603e1d9b46..2d1bf14ffbb3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -54,6 +56,8 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
@@ -71,15 +75,17 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
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.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -105,6 +111,9 @@ public class WindowDecorationTests extends ShellTestCase {
private static final int CORNER_RADIUS = 20;
private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -260,7 +269,8 @@ public class WindowDecorationTests extends ShellTestCase {
eq(0 /* index */),
eq(WindowInsets.Type.captionBar()),
eq(new Rect(100, 300, 400, 364)),
- any());
+ any(),
+ anyInt());
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -565,9 +575,9 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -654,9 +664,9 @@ public class WindowDecorationTests extends ShellTestCase {
// Never added.
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
// No need to remove them if they were never added.
verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token),
any(), eq(0) /* index */, eq(captionBar()));
@@ -681,9 +691,9 @@ public class WindowDecorationTests extends ShellTestCase {
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
windowDecor.close();
@@ -738,9 +748,9 @@ public class WindowDecorationTests extends ShellTestCase {
// Insets should be applied twice.
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -765,9 +775,30 @@ public class WindowDecorationTests extends ShellTestCase {
// Insets should only need to be applied once.
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void testRelayout_captionInsetForceConsume() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+ final WindowContainerToken token = TestRunningTaskInfoBuilder.createMockWCToken();
+ final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true);
+
+ final ActivityManager.RunningTaskInfo taskInfo =
+ builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ windowDecor.relayout(taskInfo);
+
+ // Caption inset source should be force-consuming.
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
}
@Test
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 77800a305f02..2fff4f5e9f7c 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -212,6 +212,7 @@ cc_test {
"tests/AttributeResolution_test.cpp",
"tests/BigBuffer_test.cpp",
"tests/ByteBucketArray_test.cpp",
+ "tests/CombinedIterator_test.cpp",
"tests/Config_test.cpp",
"tests/ConfigDescription_test.cpp",
"tests/ConfigLocale_test.cpp",
@@ -267,6 +268,7 @@ cc_test {
cc_benchmark {
name: "libandroidfw_benchmarks",
defaults: ["libandroidfw_defaults"],
+ test_config: "tests/AndroidTest_Benchmarks.xml",
srcs: [
// Helpers/infra for benchmarking.
"tests/BenchMain.cpp",
@@ -282,7 +284,11 @@ cc_benchmark {
"tests/Theme_bench.cpp",
],
shared_libs: common_test_libs,
- data: ["tests/data/**/*.apk"],
+ data: [
+ "tests/data/**/*.apk",
+ ":FrameworkResourcesSparseTestApp",
+ ":FrameworkResourcesNotSparseTestApp",
+ ],
}
cc_library {
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 46f636e2ae7f..822a387351e3 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -23,9 +23,11 @@
#include <map>
#include <set>
#include <span>
+#include <utility>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/CombinedIterator.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
@@ -1622,6 +1624,12 @@ Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
Theme::~Theme() = default;
+static bool IsUndefined(const Res_value& value) {
+ // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents
+ // an absence of a valid value.
+ return value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY;
+}
+
base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_NAME("Theme::ApplyStyle");
@@ -1633,39 +1641,76 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid,
// Merge the flags from this style.
type_spec_flags_ |= (*bag)->type_spec_flags;
+ //
+ // This function is the most expensive part of applying an frro to the existing app resources,
+ // and needs to be as efficient as possible.
+ // The data structure we're working with is two parallel sorted arrays of keys (resource IDs)
+ // and entries (resource value + some attributes).
+ // The styles get applied in sequence, starting with an empty set of attributes. Each style
+ // contains its values for the theme attributes, and gets applied in either normal or forced way:
+ // - normal way never overrides the existing attribute, so only unique style attributes are added
+ // - forced way overrides anything for that attribute, and if it's undefined it removes the
+ // previous value completely
+ //
+ // Style attributes come in a Bag data type - a sorted array of attributes with their values. This
+ // means we don't need to re-sort the attributes ever, and instead:
+ // - for an already existing attribute just skip it or apply the forced value
+ // - if the forced value is undefined, mark it undefined as well to get rid of it later
+ // - for a new attribute append it to the array, forming a new sorted section of new attributes
+ // past the end of the original ones (ignore undefined ones here)
+ // - inplace merge two sorted sections to form a single sorted array again.
+ // - run the last pass to remove all undefined elements
+ //
+ // Using this algorithm performs better than a repeated binary search + insert in the middle,
+ // as that keeps shifting the tail end of the arrays and wasting CPU cycles in memcpy().
+ //
+ const auto starting_size = keys_.size();
+ if (starting_size == 0) {
+ keys_.reserve((*bag)->entry_count);
+ entries_.reserve((*bag)->entry_count);
+ }
+ bool wrote_undefined = false;
for (auto it = begin(*bag); it != end(*bag); ++it) {
const uint32_t attr_res_id = it->key;
-
// If the resource ID passed in is not a style, the key can be some other identifier that is not
// a resource ID. We should fail fast instead of operating with strange resource IDs.
if (!is_valid_resid(attr_res_id)) {
return base::unexpected(std::nullopt);
}
-
- // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents
- // an absence of a valid value.
- bool is_undefined = it->value.dataType == Res_value::TYPE_NULL &&
- it->value.data != Res_value::DATA_NULL_EMPTY;
+ const bool is_undefined = IsUndefined(it->value);
if (!force && is_undefined) {
continue;
}
-
- const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
- const auto entry_it = entries_.begin() + (key_it - keys_.begin());
- if (key_it != keys_.end() && *key_it == attr_res_id) {
- if (is_undefined) {
- // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
- // true.
- keys_.erase(key_it);
- entries_.erase(entry_it);
- } else if (force) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.begin() + starting_size, attr_res_id);
+ if (key_it != keys_.begin() + starting_size && *key_it == attr_res_id) {
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+ if (force || IsUndefined(entry_it->value)) {
*entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
+ wrote_undefined |= is_undefined;
}
- } else {
- keys_.insert(key_it, attr_res_id);
- entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
+ } else if (!is_undefined) {
+ keys_.emplace_back(attr_res_id);
+ entries_.emplace_back(it->cookie, (*bag)->type_spec_flags, it->value);
}
}
+
+ if (starting_size && keys_.size() != starting_size) {
+ std::inplace_merge(
+ CombinedIterator(keys_.begin(), entries_.begin()),
+ CombinedIterator(keys_.begin() + starting_size, entries_.begin() + starting_size),
+ CombinedIterator(keys_.end(), entries_.end()));
+ }
+ if (wrote_undefined) {
+ auto new_end = std::remove_if(CombinedIterator(keys_.begin(), entries_.begin()),
+ CombinedIterator(keys_.end(), entries_.end()),
+ [](const auto& pair) { return IsUndefined(pair.second.value); });
+ keys_.erase(new_end.it1, keys_.end());
+ entries_.erase(new_end.it2, entries_.end());
+ }
+ if (android::base::kEnableDChecks && !std::is_sorted(keys_.begin(), keys_.end())) {
+ ALOGW("Bag %u was unsorted in the apk?", unsigned(resid));
+ return base::unexpected(std::nullopt);
+ }
return {};
}
@@ -1691,6 +1736,9 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid)
return std::nullopt;
}
const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+ if (IsUndefined(entry_it->value)) {
+ return std::nullopt;
+ }
type_spec_flags |= entry_it->type_spec_flags;
if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
resid = entry_it->value.data;
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index 1cb8df311c89..629f14683b19 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -132,7 +132,7 @@ bool StringPool::StyleRef::operator==(const StyleRef& rhs) const {
auto rhs_iter = rhs.entry_->spans.begin();
for (const Span& span : entry_->spans) {
- const Span& rhs_span = *rhs_iter;
+ const Span& rhs_span = *rhs_iter++;
if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char ||
span.name != rhs_span.name) {
return false;
@@ -297,24 +297,22 @@ void StringPool::Prune() {
template <typename E>
static void SortEntries(
std::vector<std::unique_ptr<E>>& entries,
- const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
+ base::function_ref<int(const StringPool::Context&, const StringPool::Context&)> cmp) {
using UEntry = std::unique_ptr<E>;
+ std::sort(entries.begin(), entries.end(), [cmp](const UEntry& a, const UEntry& b) -> bool {
+ int r = cmp(a->context, b->context);
+ if (r == 0) {
+ r = a->value.compare(b->value);
+ }
+ return r < 0;
+ });
+}
- if (cmp != nullptr) {
- std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
- int r = cmp(a->context, b->context);
- if (r == 0) {
- r = a->value.compare(b->value);
- }
- return r < 0;
- });
- } else {
- std::sort(entries.begin(), entries.end(),
- [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
- }
+void StringPool::Sort() {
+ Sort([](auto&&, auto&&) { return 0; });
}
-void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
+void StringPool::Sort(base::function_ref<int(const Context&, const Context&)> cmp) {
SortEntries(styles_, cmp);
SortEntries(strings_, cmp);
ReAssignIndices();
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 17a8ba6c03bd..ac46bc5c179f 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -280,9 +280,9 @@ class AssetManager2 {
private:
SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
- uint32_t type_flags, uint32_t resid, const ResTable_config& config) :
+ uint32_t type_flags, uint32_t resid, ResTable_config config) :
cookie(cookie), data(value_data), type(value_type), flags(type_flags),
- resid(resid), config(config) {};
+ resid(resid), config(std::move(config)) {}
};
// Retrieves the best matching resource value with ID `resid`.
diff --git a/libs/androidfw/include/androidfw/CombinedIterator.h b/libs/androidfw/include/androidfw/CombinedIterator.h
new file mode 100644
index 000000000000..4ff6a7d7e6c9
--- /dev/null
+++ b/libs/androidfw/include/androidfw/CombinedIterator.h
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <compare>
+#include <iterator>
+#include <utility>
+
+namespace android {
+
+namespace detail {
+// A few useful aliases to not repeat them everywhere
+template <class It1, class It2>
+using Value = std::pair<typename std::iterator_traits<It1>::value_type,
+ typename std::iterator_traits<It2>::value_type>;
+
+template <class It1, class It2>
+using BaseRefPair = std::pair<typename std::iterator_traits<It1>::reference,
+ typename std::iterator_traits<It2>::reference>;
+
+template <class It1, class It2>
+struct RefPair : BaseRefPair<It1, It2> {
+ using Base = BaseRefPair<It1, It2>;
+ using Value = detail::Value<It1, It2>;
+
+ RefPair(It1 it1, It2 it2) : Base(*it1, *it2) {
+ }
+
+ RefPair& operator=(const Value& v) {
+ this->first = v.first;
+ this->second = v.second;
+ return *this;
+ }
+ operator Value() const {
+ return Value(this->first, this->second);
+ }
+ bool operator==(const RefPair& other) {
+ return this->first == other.first;
+ }
+ bool operator==(const Value& other) {
+ return this->first == other.first;
+ }
+ std::strong_ordering operator<=>(const RefPair& other) const {
+ return this->first <=> other.first;
+ }
+ std::strong_ordering operator<=>(const Value& other) const {
+ return this->first <=> other.first;
+ }
+ friend void swap(RefPair& l, RefPair& r) {
+ using std::swap;
+ swap(l.first, r.first);
+ swap(l.second, r.second);
+ }
+};
+
+template <class It1, class It2>
+struct RefPairPtr {
+ RefPair<It1, It2> value;
+
+ RefPair<It1, It2>* operator->() const {
+ return &value;
+ }
+};
+} // namespace detail
+
+//
+// CombinedIterator - a class to combine two iterators to process them as a single iterator to a
+// pair of values. Useful for processing a data structure of "struct of arrays", replacing
+// array of structs for cache locality.
+//
+// The value type is a pair of copies of the values of each iterator, and the reference is a
+// pair of references to the corresponding values. Comparison only compares the first element,
+// making it most useful for using on data like (vector<Key>, vector<Value>) for binary searching,
+// sorting both together and so on.
+//
+// The class is designed for handling arrays, so it requires random access iterators as an input.
+//
+
+template <class It1, class It2>
+requires std::random_access_iterator<It1> && std::random_access_iterator<It2>
+struct CombinedIterator {
+ typedef detail::Value<It1, It2> value_type;
+ typedef detail::RefPair<It1, It2> reference;
+ typedef std::ptrdiff_t difference_type;
+ typedef detail::RefPairPtr<It1, It2> pointer;
+ typedef std::random_access_iterator_tag iterator_category;
+
+ CombinedIterator(It1 it1 = {}, It2 it2 = {}) : it1(it1), it2(it2) {
+ }
+
+ bool operator<(const CombinedIterator& other) const {
+ return it1 < other.it1;
+ }
+ bool operator<=(const CombinedIterator& other) const {
+ return it1 <= other.it1;
+ }
+ bool operator>(const CombinedIterator& other) const {
+ return it1 > other.it1;
+ }
+ bool operator>=(const CombinedIterator& other) const {
+ return it1 >= other.it1;
+ }
+ bool operator==(const CombinedIterator& other) const {
+ return it1 == other.it1;
+ }
+ pointer operator->() const {
+ return pointer{{it1, it2}};
+ }
+ reference operator*() const {
+ return {it1, it2};
+ }
+ reference operator[](difference_type n) const {
+ return {it1 + n, it2 + n};
+ }
+
+ CombinedIterator& operator++() {
+ ++it1;
+ ++it2;
+ return *this;
+ }
+ CombinedIterator operator++(int) {
+ const auto res = *this;
+ ++*this;
+ return res;
+ }
+ CombinedIterator& operator--() {
+ --it1;
+ --it2;
+ return *this;
+ }
+ CombinedIterator operator--(int) {
+ const auto res = *this;
+ --*this;
+ return res;
+ }
+ CombinedIterator& operator+=(difference_type n) {
+ it1 += n;
+ it2 += n;
+ return *this;
+ }
+ CombinedIterator operator+(difference_type n) const {
+ CombinedIterator res = *this;
+ return res += n;
+ }
+
+ CombinedIterator& operator-=(difference_type n) {
+ it1 -= n;
+ it2 -= n;
+ return *this;
+ }
+ CombinedIterator operator-(difference_type n) const {
+ CombinedIterator res = *this;
+ return res -= n;
+ }
+ difference_type operator-(const CombinedIterator& other) {
+ return it1 - other.it1;
+ }
+
+ It1 it1;
+ It2 it2;
+};
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 0190ab57bf23..9b2c72a29f48 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -17,7 +17,6 @@
#ifndef _ANDROID_STRING_POOL_H
#define _ANDROID_STRING_POOL_H
-#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
@@ -25,6 +24,7 @@
#include "BigBuffer.h"
#include "IDiagnostics.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
@@ -205,7 +205,8 @@ class StringPool {
// Sorts the strings according to their Context using some comparison function.
// Equal Contexts are further sorted by string value, lexicographically.
// If no comparison function is provided, values are only sorted lexicographically.
- void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr);
+ void Sort();
+ void Sort(base::function_ref<int(const Context&, const Context&)> cmp);
// Removes any strings that have no references.
void Prune();
diff --git a/libs/androidfw/tests/AndroidTest_Benchmarks.xml b/libs/androidfw/tests/AndroidTest_Benchmarks.xml
new file mode 100644
index 000000000000..e61e46fb7785
--- /dev/null
+++ b/libs/androidfw/tests/AndroidTest_Benchmarks.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<configuration description="Runs libandroidfw_benchmarks and libandroidfw_tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native-metric" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="libandroidfw_benchmarks->/data/local/tmp/libandroidfw_benchmarks" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+ <option name="native-benchmark-device-path" value="/data/local/tmp" />
+ <option name="benchmark-module-name" value="libandroidfw_benchmarks" />
+ <!-- The GoogleBenchmarkTest class ordinarily expects every file in the benchmark's
+ directory (recursively) to be a google-benchmark binary, so we need this setting to
+ avoid failing on the test data files. -->
+ <option name="file-exclusion-filter-regex" value=".*\.(apk|config)$" />
+ </test>
+</configuration> \ No newline at end of file
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 2caa98c35971..136f5ea639a1 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -37,7 +37,7 @@ constexpr const static char* kFrameworkPath = "/system/framework/framework-res.a
static void BM_AssetManagerLoadAssets(benchmark::State& state) {
std::string path = GetTestDataPath() + "/basic/basic.apk";
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
auto apk = ApkAssets::Load(path);
AssetManager2 assets;
assets.SetApkAssets({apk});
@@ -47,7 +47,7 @@ BENCHMARK(BM_AssetManagerLoadAssets);
static void BM_AssetManagerLoadAssetsOld(benchmark::State& state) {
String8 path((GetTestDataPath() + "/basic/basic.apk").data());
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
AssetManager assets;
assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
false /* isSystemAsset */);
@@ -60,7 +60,7 @@ BENCHMARK(BM_AssetManagerLoadAssetsOld);
static void BM_AssetManagerLoadFrameworkAssets(benchmark::State& state) {
std::string path = kFrameworkPath;
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
auto apk = ApkAssets::Load(path);
AssetManager2 assets;
assets.SetApkAssets({apk});
@@ -70,7 +70,7 @@ BENCHMARK(BM_AssetManagerLoadFrameworkAssets);
static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) {
String8 path(kFrameworkPath);
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
AssetManager assets;
assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
false /* isSystemAsset */);
@@ -138,7 +138,7 @@ static void BM_AssetManagerGetBag(benchmark::State& state) {
AssetManager2 assets;
assets.SetApkAssets({apk});
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
auto bag = assets.GetBag(app::R::style::StyleTwo);
if (!bag.has_value()) {
state.SkipWithError("Failed to load get bag");
@@ -165,7 +165,7 @@ static void BM_AssetManagerGetBagOld(benchmark::State& state) {
const ResTable& table = assets.getResources(true);
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
const ResTable::bag_entry* bag_begin;
const ssize_t N = table.lockBag(app::R::style::StyleTwo, &bag_begin);
const ResTable::bag_entry* const bag_end = bag_begin + N;
@@ -190,7 +190,7 @@ static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
AssetManager2 assets;
assets.SetApkAssets({apk});
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
std::set<std::string> locales =
assets.GetResourceLocales(false /*exclude_system*/, true /*merge_equivalent_languages*/);
benchmark::DoNotOptimize(locales);
@@ -208,7 +208,7 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) {
const ResTable& table = assets.getResources(true);
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
Vector<String8> locales;
table.getLocales(&locales, true /*includeSystemLocales*/, true /*mergeEquivalentLangs*/);
benchmark::DoNotOptimize(locales);
@@ -231,7 +231,7 @@ static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
std::vector<ResTable_config> configs;
configs.push_back(config);
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
configs[0].sdkVersion = ~configs[0].sdkVersion;
assets.SetConfigurations(configs);
}
@@ -251,7 +251,7 @@ static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state)
ResTable_config config;
memset(&config, 0, sizeof(config));
- while (state.KeepRunning()) {
+ for (auto&& _ : state) {
config.sdkVersion = ~config.sdkVersion;
assets.setConfiguration(config);
}
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 8b883f4ed1df..e3fc0a0a4e68 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -28,7 +28,7 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab
for (const std::string& path : paths) {
if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
false /* appAsLib */, false /* isSystemAssets */)) {
- state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+ state.SkipWithError(base::StringPrintf("Failed to old-load assets %s", path.c_str()).c_str());
return;
}
}
@@ -57,7 +57,7 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_
for (const std::string& path : paths) {
auto apk = ApkAssets::Load(path);
if (apk == nullptr) {
- state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+ state.SkipWithError(base::StringPrintf("Failed to new-load assets %s", path.c_str()).c_str());
return;
}
apk_assets.push_back(std::move(apk));
diff --git a/libs/androidfw/tests/CombinedIterator_test.cpp b/libs/androidfw/tests/CombinedIterator_test.cpp
new file mode 100644
index 000000000000..c1228f34625f
--- /dev/null
+++ b/libs/androidfw/tests/CombinedIterator_test.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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 "androidfw/CombinedIterator.h"
+
+#include <algorithm>
+#include <string>
+#include <strstream>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace android {
+
+template <class Coll>
+std::string toString(const Coll& coll) {
+ std::stringstream res;
+ res << "(" << std::size(coll) << ")";
+ if (std::size(coll)) {
+ res << "{" << coll[0];
+ for (int i = 1; i != std::size(coll); ++i) {
+ res << "," << coll[i];
+ }
+ res << "}";
+ }
+ return res.str();
+}
+
+template <class Coll>
+void AssertCollectionEq(const Coll& first, const Coll& second) {
+ ASSERT_EQ(std::size(first), std::size(second))
+ << "first: " << toString(first) << ", second: " << toString(second);
+ for (int i = 0; i != std::size(first); ++i) {
+ ASSERT_EQ(first[i], second[i])
+ << "index: " << i << " first: " << toString(first) << ", second: " << toString(second);
+ }
+}
+
+TEST(CombinedIteratorTest, Sorting) {
+ std::vector<int> v1 = {2, 1, 3, 4, 0};
+ std::vector<int> v2 = {20, 10, 30, 40, 0};
+
+ std::sort(CombinedIterator(v1.begin(), v2.begin()), CombinedIterator(v1.end(), v2.end()));
+
+ ASSERT_EQ(v1.size(), v2.size());
+ ASSERT_TRUE(std::is_sorted(v1.begin(), v1.end()));
+ ASSERT_TRUE(std::is_sorted(v2.begin(), v2.end()));
+ AssertCollectionEq(v1, {0, 1, 2, 3, 4});
+ AssertCollectionEq(v2, {0, 10, 20, 30, 40});
+}
+
+TEST(CombinedIteratorTest, Removing) {
+ std::vector<int> v1 = {1, 2, 3, 4, 5, 5, 5, 6};
+ std::vector<int> v2 = {10, 20, 30, 40, 50, 50, 50, 60};
+
+ auto newEnd =
+ std::remove_if(CombinedIterator(v1.begin(), v2.begin()), CombinedIterator(v1.end(), v2.end()),
+ [](auto&& pair) { return pair.first >= 3 && pair.first <= 5; });
+
+ ASSERT_EQ(newEnd.it1, v1.begin() + 3);
+ ASSERT_EQ(newEnd.it2, v2.begin() + 3);
+
+ v1.erase(newEnd.it1, v1.end());
+ AssertCollectionEq(v1, {1, 2, 6});
+ v2.erase(newEnd.it2, v2.end());
+ AssertCollectionEq(v2, {10, 20, 60});
+}
+
+TEST(CombinedIteratorTest, InplaceMerge) {
+ std::vector<int> v1 = {1, 3, 4, 7, 2, 5, 6};
+ std::vector<int> v2 = {10, 30, 40, 70, 20, 50, 60};
+
+ std::inplace_merge(CombinedIterator(v1.begin(), v2.begin()),
+ CombinedIterator(v1.begin() + 4, v2.begin() + 4),
+ CombinedIterator(v1.end(), v2.end()));
+ ASSERT_TRUE(std::is_sorted(v1.begin(), v1.end()));
+ ASSERT_TRUE(std::is_sorted(v2.begin(), v2.end()));
+
+ AssertCollectionEq(v1, {1, 2, 3, 4, 5, 6, 7});
+ AssertCollectionEq(v2, {10, 20, 30, 40, 50, 60, 70});
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/Theme_bench.cpp b/libs/androidfw/tests/Theme_bench.cpp
index dfbb5a76dec6..bf89617635cc 100644
--- a/libs/androidfw/tests/Theme_bench.cpp
+++ b/libs/androidfw/tests/Theme_bench.cpp
@@ -27,6 +27,10 @@ constexpr const static char* kFrameworkPath = "/system/framework/framework-res.a
constexpr const static uint32_t kStyleId = 0x01030237u; // android:style/Theme.Material.Light
constexpr const static uint32_t kAttrId = 0x01010030u; // android:attr/colorForeground
+constexpr const static uint32_t kStyle2Id = 0x01030224u; // android:style/Theme.Material
+constexpr const static uint32_t kStyle3Id = 0x0103024du; // android:style/Widget.Material
+constexpr const static uint32_t kStyle4Id = 0x0103028eu; // android:style/Widget.Material.Light
+
static void BM_ThemeApplyStyleFramework(benchmark::State& state) {
auto apk = ApkAssets::Load(kFrameworkPath);
if (apk == nullptr) {
@@ -61,6 +65,32 @@ static void BM_ThemeApplyStyleFrameworkOld(benchmark::State& state) {
}
BENCHMARK(BM_ThemeApplyStyleFrameworkOld);
+static void BM_ThemeRebaseFramework(benchmark::State& state) {
+ auto apk = ApkAssets::Load(kFrameworkPath);
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk});
+
+ // Create two arrays of styles to switch between back and forth.
+ const uint32_t styles1[] = {kStyle2Id, kStyleId, kStyle3Id};
+ const uint8_t force1[std::size(styles1)] = {false, true, false};
+ const uint32_t styles2[] = {kStyleId, kStyle2Id, kStyle4Id, kStyle3Id};
+ const uint8_t force2[std::size(styles2)] = {false, true, true, false};
+ const auto theme = assets.NewTheme();
+ // Initialize the theme to make the first iteration the same as the rest.
+ theme->Rebase(&assets, styles1, force1, std::size(force1));
+
+ while (state.KeepRunning()) {
+ theme->Rebase(&assets, styles2, force2, std::size(force2));
+ theme->Rebase(&assets, styles1, force1, std::size(force1));
+ }
+}
+BENCHMARK(BM_ThemeRebaseFramework);
+
static void BM_ThemeGetAttribute(benchmark::State& state) {
auto apk = ApkAssets::Load(kFrameworkPath);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 341599e79662..e302fa8b1fc3 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -114,16 +114,12 @@ cc_defaults {
"libbase",
"libharfbuzz_ng",
"libminikin",
- "server_configurable_flags",
- "libaconfig_storage_read_api_cc"
],
static_libs: [
"libui-types",
],
- whole_static_libs: ["hwui_flags_cc_lib"],
-
target: {
android: {
shared_libs: [
@@ -145,6 +141,8 @@ cc_defaults {
"libsync",
"libui",
"aconfig_text_flags_c_lib",
+ "server_configurable_flags",
+ "libaconfig_storage_read_api_cc",
],
static_libs: [
"libEGL_blobCache",
@@ -155,6 +153,7 @@ cc_defaults {
"libstatssocket_lazy",
"libtonemap",
],
+ whole_static_libs: ["hwui_flags_cc_lib"],
},
host: {
static_libs: [
@@ -419,7 +418,6 @@ cc_defaults {
],
static_libs: [
- "libnativehelper_lazy",
"libziparchive_for_incfs",
],
@@ -446,6 +444,7 @@ cc_defaults {
],
static_libs: [
"libgif",
+ "libnativehelper_lazy",
"libstatslog_hwui",
"libstatspull_lazy",
"libstatssocket_lazy",
@@ -464,6 +463,7 @@ cc_defaults {
],
static_libs: [
"libandroidfw",
+ "libnativehelper_jvm",
],
},
},
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 70a9ef04d6f3..073bc8de3659 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -41,6 +41,7 @@ extern int register_android_graphics_Shader(JNIEnv* env);
extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
extern int register_android_graphics_YuvImage(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env);
namespace android {
@@ -51,6 +52,8 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
@@ -72,6 +75,7 @@ extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
#define REG_JNI(name) { name }
struct RegJNIRec {
@@ -95,7 +99,11 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
{"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+ {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+ {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)},
+ {"android.graphics.HardwareRendererObserver",
+ REG_JNI(register_android_graphics_HardwareRendererObserver)},
{"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
{"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
@@ -118,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
{"android.graphics.animation.RenderNodeAnimator",
REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
+ {"android.graphics.drawable.AnimatedImageDrawable",
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable)},
{"android.graphics.drawable.AnimatedVectorDrawable",
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
{"android.graphics.drawable.VectorDrawable",
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index cfca48084d97..0efb2c81af01 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -17,7 +17,6 @@
#include <SkFontMetrics.h>
#include <SkRRect.h>
#include <SkTextBlob.h>
-#include <com_android_graphics_hwui_flags.h>
#include "../utils/Color.h"
#include "Canvas.h"
@@ -30,7 +29,19 @@
#include "hwui/PaintFilter.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
namespace flags = com::android::graphics::hwui::flags;
+#else
+namespace flags {
+constexpr bool high_contrast_text_luminance() {
+ return false;
+}
+constexpr bool high_contrast_text_small_text_rect() {
+ return false;
+}
+} // namespace flags
+#endif
namespace android {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 0d0af1110ca4..4d185c69c172 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -28,8 +28,8 @@
#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
#include <ui/FatVector.h>
-#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
#include <sstream>
@@ -141,7 +141,8 @@ VulkanManager::~VulkanManager() {
mPhysicalDeviceFeatures2 = {};
}
-void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) {
+void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions,
+ VkPhysicalDeviceFeatures2& features) {
VkResult err;
constexpr VkApplicationInfo app_info = {
@@ -506,7 +507,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
return vkGetInstanceProcAddr(instance, proc_name);
};
- GrVkBackendContext backendContext;
+ skgpu::VulkanBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
backendContext.fDevice = mDevice;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b92ebb3cdf71..08f9d4253d7e 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -24,8 +24,7 @@
#include <SkSurface.h>
#include <android-base/unique_fd.h>
#include <utils/StrongPointer.h>
-#include <vk/GrVkBackendContext.h>
-#include <vk/GrVkExtensions.h>
+#include <vk/VulkanExtensions.h>
#include <vulkan/vulkan.h>
// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
@@ -127,7 +126,7 @@ private:
// Sets up the VkInstance and VkDevice objects. Also fills out the passed in
// VkPhysicalDeviceFeatures struct.
- void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&);
+ void setupDevice(skgpu::VulkanExtensions&, VkPhysicalDeviceFeatures2&);
// simple wrapper class that exists only to initialize a pointer to NULL
template <typename FNPTR_TYPE>
@@ -206,7 +205,7 @@ private:
BufferAge,
};
SwapBehavior mSwapBehavior = SwapBehavior::Discard;
- GrVkExtensions mExtensions;
+ skgpu::VulkanExtensions mExtensions;
uint32_t mDriverVersion = 0;
std::once_flag mInitFlag;