summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/Android.bp18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java54
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java63
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java26
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java53
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java95
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java10
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java29
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java24
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-core-release.aarbin3714 -> 0 bytes
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin41055 -> 0 bytes
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/OWNERS2
-rw-r--r--libs/WindowManager/Shell/res/color-night/taskbar_background.xml2
-rw-r--r--libs/WindowManager/Shell/res/color/taskbar_background.xml2
-rw-r--r--libs/WindowManager/Shell/res/color/unfold_background.xml4
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_floating_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_more_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_select_button.xml30
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml11
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_close_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml34
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml24
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml92
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml136
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml (renamed from libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml)26
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml57
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml47
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml62
-rw-r--r--libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml10
-rw-r--r--libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml66
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings_tv.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml17
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings_tv.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml19
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings_tv.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values/attrs.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml13
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml41
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java419
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/StatusBarCustomizer.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java668
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java194
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java282
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java100
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java233
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java267
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java212
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java254
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java154
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java135
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java)5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java)72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java269
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java269
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java335
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt272
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java230
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java171
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java91
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java250
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java200
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java38
-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.java290
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java208
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java857
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java524
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java508
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java688
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java165
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java)6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java)14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java)13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java516
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java281
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java488
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java196
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt28
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt89
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt91
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt113
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt38
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt28
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java135
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java232
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java270
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java)9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java)125
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java119
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java108
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java159
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java151
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java68
-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/pip/tv/TvPipMenuControllerTest.java336
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java70
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java)4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt92
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java154
-rw-r--r--libs/dream/lowlight/Android.bp6
-rw-r--r--libs/dream/lowlight/res/values/config.xml3
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java117
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt138
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt118
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java61
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt82
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt9
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt8
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt29
-rw-r--r--libs/dream/lowlight/tests/Android.bp2
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java98
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt169
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt184
-rw-r--r--libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt125
-rw-r--r--libs/hwui/Android.bp10
-rw-r--r--libs/hwui/Gainmap.cpp32
-rw-r--r--libs/hwui/Gainmap.h1
-rw-r--r--libs/hwui/MemoryPolicy.h1
-rw-r--r--libs/hwui/Properties.cpp9
-rw-r--r--libs/hwui/Properties.h4
-rw-r--r--libs/hwui/RecordingCanvas.cpp17
-rw-r--r--libs/hwui/SkiaCanvas.cpp26
-rw-r--r--libs/hwui/effects/GainmapRenderer.cpp253
-rw-r--r--libs/hwui/effects/GainmapRenderer.h5
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp33
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.h17
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp11
-rw-r--r--libs/hwui/hwui/ImageDecoder.h2
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp6
-rw-r--r--libs/hwui/jni/Bitmap.cpp11
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp5
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp5
-rw-r--r--libs/hwui/jni/Graphics.cpp16
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp9
-rw-r--r--libs/hwui/jni/Shader.cpp21
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.cpp32
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.h6
-rw-r--r--libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp22
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp52
-rw-r--r--libs/hwui/jni/fonts/Font.cpp5
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp48
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h8
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp7
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp15
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h2
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp6
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp22
-rw-r--r--libs/hwui/renderthread/CanvasContext.h5
-rw-r--r--libs/hwui/renderthread/EglManager.cpp20
-rw-r--r--libs/hwui/renderthread/HardwareBufferRenderParams.h12
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h2
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp5
-rw-r--r--libs/hwui/renderthread/RenderThread.h1
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp40
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp23
-rw-r--r--libs/hwui/renderthread/VulkanSurface.h2
-rw-r--r--libs/input/MouseCursorController.cpp76
-rw-r--r--libs/input/MouseCursorController.h10
-rw-r--r--libs/input/PointerController.cpp59
-rw-r--r--libs/input/PointerController.h28
-rw-r--r--libs/input/PointerControllerContext.h2
-rw-r--r--libs/input/tests/PointerController_test.cpp6
428 files changed, 17076 insertions, 5104 deletions
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192cd7ceb..abe8f859f2fe 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -55,20 +55,6 @@ prebuilt_etc {
}
// Extensions
-// NOTE: This module is still under active development and must not
-// be used in production. Use 'androidx.window.sidecar' instead.
-android_library_import {
- name: "window-extensions",
- aars: ["window-extensions-release.aar"],
- sdk_version: "current",
-}
-
-android_library_import {
- name: "window-extensions-core",
- aars: ["window-extensions-core-release.aar"],
- sdk_version: "current",
-}
-
java_library {
name: "androidx.window.extensions",
srcs: [
@@ -77,8 +63,8 @@ java_library {
"src/androidx/window/common/**/*.java",
],
static_libs: [
- "window-extensions",
- "window-extensions-core",
+ "androidx.window.extensions_extensions-nodeps",
+ "androidx.window.extensions.core_core-nodeps",
],
installable: true,
sdk_version: "core_platform",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 68ff806c6765..65955b1d9bcc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -21,6 +21,7 @@ import static androidx.window.util.ExtensionHelper.isZero;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -33,7 +34,8 @@ import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** A representation of a folding feature for both Extension and Sidecar.
+/**
+ * A representation of a folding feature for both Extension and Sidecar.
* For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
* {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
* {@link androidx.window.extensions.layout.FoldingFeature}.
@@ -67,10 +69,11 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_UNKNOWN = -1;
/**
- * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
- * and Extensions do not match exactly.
+ * A common state that contains no folding features. For example, an in-folding device in the
+ * "closed" device state.
*/
- public static final int COMMON_STATE_FLAT = 3;
+ public static final int COMMON_STATE_NO_FOLDING_FEATURES = 1;
+
/**
* A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
* Sidecar and Extensions do not match exactly.
@@ -78,9 +81,27 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_HALF_OPENED = 2;
/**
- * The possible states for a folding hinge.
+ * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
+ * and Extensions do not match exactly.
+ */
+ public static final int COMMON_STATE_FLAT = 3;
+
+ /**
+ * A common state where the hinge state should be derived using the base state from
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)} instead of the
+ * emulated state. This is an internal state and must not be passed to clients.
*/
- @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
+ public static final int COMMON_STATE_USE_BASE_STATE = 1000;
+
+ /**
+ * The possible states for a folding hinge. Common in this context means normalized between
+ * extensions and sidecar.
+ */
+ @IntDef({COMMON_STATE_UNKNOWN,
+ COMMON_STATE_NO_FOLDING_FEATURES,
+ COMMON_STATE_HALF_OPENED,
+ COMMON_STATE_FLAT,
+ COMMON_STATE_USE_BASE_STATE})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
@@ -167,7 +188,7 @@ public final class CommonFoldingFeature {
}
String stateString = featureMatcher.group(6);
stateString = stateString == null ? "" : stateString;
- final int state;
+ @State final int state;
switch (stateString) {
case PATTERN_STATE_FLAT:
state = COMMON_STATE_FLAT;
@@ -191,8 +212,8 @@ public final class CommonFoldingFeature {
@NonNull
private final Rect mRect;
- CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
- assertValidState(state);
+ CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
+ assertReportableState(state);
this.mType = type;
this.mState = state;
if (rect.width() == 0 && rect.height() == 0) {
@@ -231,13 +252,22 @@ public final class CommonFoldingFeature {
}
@Override
+ public String toString() {
+ return "CommonFoldingFeature=[Type: " + mType + ", state: " + mState + "]";
+ }
+
+ @Override
public int hashCode() {
return Objects.hash(mType, mState, mRect);
}
- private static void assertValidState(@Nullable Integer state) {
- if (state != null && state != COMMON_STATE_FLAT
- && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
+ /**
+ * Checks if the provided folding feature state should be reported to clients. See
+ * {@link androidx.window.extensions.layout.FoldingFeature}
+ */
+ private static void assertReportableState(@State int state) {
+ if (state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED
+ && state != COMMON_STATE_UNKNOWN) {
throw new IllegalArgumentException("Invalid state: " + state
+ "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 0bdf98c8680f..66f27f517ab3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,6 +19,7 @@ package androidx.window.common;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
@@ -52,13 +53,54 @@ public final class DeviceStateManagerFoldingFeatureProducer
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+ * {@link CommonFoldingFeature.State} map.
+ */
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+ /**
+ * Emulated device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+ * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
+ * physical device states. They represent the state of the device when various software
+ * features and APIs are applied. The emulated states generally consist of all "base" states,
+ * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
+ * example is activated via public API and can be active in both the "open" and "half folded"
+ * device states.
+ */
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ /**
+ * Base device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
+ * "Base" in this context means the "physical" state of the device.
+ */
+ private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+
@NonNull
private final BaseDataProducer<String> mRawFoldSupplier;
+ private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+ @Override
+ public void onStateChanged(int state) {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ mCurrentBaseDeviceState = state;
+
+ if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
+ == COMMON_STATE_USE_BASE_STATE) {
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+ }
+ };
+
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
@NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
@@ -92,12 +134,8 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
if (mDeviceStateToPostureMap.size() > 0) {
- DeviceStateCallback deviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
- };
Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
- .registerCallback(context.getMainExecutor(), deviceStateCallback);
+ .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
}
}
@@ -178,11 +216,18 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
- final int globalHingeState = globalHingeState();
- return parseListFromString(displayFeaturesString, globalHingeState);
+ return parseListFromString(displayFeaturesString, currentHingeState());
}
- private int globalHingeState() {
- return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+ @CommonFoldingFeature.State
+ private int currentHingeState() {
+ @CommonFoldingFeature.State
+ int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+
+ if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
+ posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+ }
+
+ return posture;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 274dcaee5a43..cc46a4bc4ea3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -129,12 +129,9 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
* {@link WindowAreaComponent#STATUS_AVAILABLE} or
* {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
* state respectively. When the rear display feature is triggered, the status is updated to be
- * {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+ * {@link WindowAreaComponent#STATUS_ACTIVE}.
* TODO(b/240727590): Prefix with AREA_
*
- * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
- * enabled.
- *
* @param consumer {@link Consumer} interested in receiving updates to the status of
* rear display mode.
*/
@@ -263,8 +260,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
return;
}
@WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
+ DisplayMetrics metrics =
+ currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
consumer.accept(
- new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics()));
+ new RearDisplayPresentationStatus(currentStatus, metrics));
}
}
@@ -405,14 +404,21 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
}
}
-
@GuardedBy("mLock")
private int getCurrentRearDisplayModeStatus() {
+ if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
+ if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)) {
+ return WindowAreaComponent.STATUS_UNAVAILABLE;
+ }
+
if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
- || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
|| isRearDisplayActive()) {
- return WindowAreaComponent.STATUS_UNAVAILABLE;
+ return WindowAreaComponent.STATUS_ACTIVE;
}
+
return WindowAreaComponent.STATUS_AVAILABLE;
}
@@ -441,6 +447,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int getCurrentRearDisplayPresentationModeStatus() {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
if (mCurrentDeviceState == mConcurrentDisplayState
|| !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
|| isDeviceFolded()) {
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 caefb2e9516c..89f4890c254e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,7 +40,9 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
+import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -1000,7 +1002,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (container != null) {
+ // Looking for the activity below from the information we already have if the container
+ // only embeds activities of the same process because activities of other processes are not
+ // available in this embedding host process for security concern.
+ if (container != null && !container.hasCrossProcessActivities()) {
final List<Activity> containerActivities = container.collectNonFinishingActivities();
final int index = containerActivities.indexOf(activity);
if (index > 0) {
@@ -1034,9 +1039,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer primaryContainer = getContainerWithActivity(
primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
- final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+ final TaskContainer.TaskProperties taskProperties = mPresenter
+ .getTaskProperties(primaryActivity);
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+ getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
- && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
+ && canReuseContainer(splitRule, splitContainer.getSplitRule(),
+ getTaskWindowMetrics(taskProperties.getConfiguration()),
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1055,7 +1066,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
// Create new split pair.
- mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
+ mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule,
+ calculatedSplitAttributes);
return true;
}
@@ -1280,9 +1292,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
- final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+ final TaskContainer.TaskProperties taskProperties = mPresenter
+ .getTaskProperties(primaryActivity);
+ final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
+ taskProperties.getConfiguration());
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+ getActivityIntentMinDimensionsPair(primaryActivity, intent));
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
- && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
+ && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1293,7 +1312,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// Create a new TaskFragment to split with the primary activity for the new activity.
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
- splitRule);
+ splitRule, calculatedSplitAttributes);
}
/**
@@ -2270,21 +2289,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
- * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
- * there is any.
+ * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
+ * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
+ * {@link SplitContainer} if there is any.
*/
private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
- @NonNull WindowMetrics parentWindowMetrics) {
+ @NonNull WindowMetrics parentWindowMetrics,
+ @NonNull SplitAttributes calculatedSplitAttributes,
+ @NonNull SplitAttributes containerSplitAttributes) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
- return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
- parentWindowMetrics);
+ return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+ parentWindowMetrics)
+ // Besides rules, we should also check whether the SplitContainer's splitAttributes
+ // matches the current splitAttributes or not. The splitAttributes may change
+ // if the app chooses different SplitAttributes calculator function before a new
+ // activity is started even they match the same splitRule.
+ && calculatedSplitAttributes.equals(containerSplitAttributes);
}
/** Whether the two rules have the same presentation. */
@VisibleForTesting
- static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
if (rule1.getTag() != null || rule2.getTag() != null) {
// Tag must be unique if it is set. We don't want to reuse the container if the rules
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 040851181e92..53d39d9fa28e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -174,12 +174,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
TaskFragmentContainer createNewSplitWithEmptySideContainer(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
- @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
+ @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule,
+ @NonNull SplitAttributes splitAttributes) {
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
- final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
- primaryActivity, secondaryIntent);
- final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -217,15 +214,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* same container as the primary activity, a new container will be
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
+ * @param splitAttributes The {@link SplitAttributes} to apply
*/
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
- @NonNull SplitPairRule rule) {
+ @NonNull SplitPairRule rule, @NonNull SplitAttributes splitAttributes) {
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
- final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
- secondaryActivity);
- final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -654,7 +648,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- private static Pair<Size, Size> getActivitiesMinDimensionsPair(
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@@ -716,24 +710,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitAttributes splitAttributes) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
- final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
- foldingFeature);
- final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
- .setSplitType(splitType)
- .setLayoutDirection(splitAttributes.getLayoutDirection())
- .build();
- if (!shouldShowSplit(computedSplitAttributes)) {
+ if (!shouldShowSplit(splitAttributes)) {
return new Rect();
}
final Rect bounds;
switch (position) {
case POSITION_START:
- bounds = getPrimaryBounds(taskConfiguration, computedSplitAttributes,
- foldingFeature);
+ bounds = getPrimaryBounds(taskConfiguration, splitAttributes, foldingFeature);
break;
case POSITION_END:
- bounds = getSecondaryBounds(taskConfiguration, computedSplitAttributes,
- foldingFeature);
+ bounds = getSecondaryBounds(taskConfiguration, splitAttributes, foldingFeature);
break;
case POSITION_FILL:
default:
@@ -748,63 +734,76 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
- if (!shouldShowSplit(splitAttributes)) {
+ final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes,
+ computeSplitType(splitAttributes, taskConfiguration, foldingFeature));
+ if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
- switch (splitAttributes.getLayoutDirection()) {
+ switch (computedSplitAttributes.getLayoutDirection()) {
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
- return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
- return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getRightContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.LOCALE: {
final boolean isLtr = taskConfiguration.getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
return isLtr
- ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
- : getRightContainerBounds(taskConfiguration, splitAttributes,
+ ? getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature)
+ : getRightContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
}
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
- return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getTopContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
- return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getBottomContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
default:
throw new IllegalArgumentException("Unknown layout direction:"
- + splitAttributes.getLayoutDirection());
+ + computedSplitAttributes.getLayoutDirection());
}
}
@NonNull
private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
- if (!shouldShowSplit(splitAttributes)) {
+ final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes,
+ computeSplitType(splitAttributes, taskConfiguration, foldingFeature));
+ if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
- switch (splitAttributes.getLayoutDirection()) {
+ switch (computedSplitAttributes.getLayoutDirection()) {
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
- return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getRightContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
- return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.LOCALE: {
final boolean isLtr = taskConfiguration.getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
return isLtr
- ? getRightContainerBounds(taskConfiguration, splitAttributes,
+ ? getRightContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature)
- : getLeftContainerBounds(taskConfiguration, splitAttributes,
+ : getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
}
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
- return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getBottomContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
- return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getTopContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
default:
throw new IllegalArgumentException("Unknown layout direction:"
@@ -812,6 +811,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
}
+ /**
+ * Returns the {@link SplitAttributes} that update the {@link SplitType} to
+ * {@code splitTypeToUpdate}.
+ */
+ private static SplitAttributes updateSplitAttributesType(
+ @NonNull SplitAttributes splitAttributes, @NonNull SplitType splitTypeToUpdate) {
+ return new SplitAttributes.Builder()
+ .setSplitType(splitTypeToUpdate)
+ .setLayoutDirection(splitAttributes.getLayoutDirection())
+ .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .build();
+ }
+
@NonNull
private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
@@ -904,7 +916,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Nullable
- private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
+ @VisibleForTesting
+ FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
final int displayId = taskProperties.getDisplayId();
final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
.windowConfiguration;
@@ -1027,7 +1040,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+ static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
// TODO(b/190433398): Supply correct insets.
final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index dcc12ac07589..b917ac80256c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -215,6 +215,8 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
} else {
closingTargets.add(target);
closingWholeScreenBounds.union(target.screenSpaceBounds);
+ // Union the start bounds since this may be the ClosingChanging animation.
+ closingWholeScreenBounds.union(target.startBounds);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 6c553a836dbd..60be9d16d749 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -153,6 +153,11 @@ class TaskFragmentContainer {
Runnable mAppearEmptyTimeout;
/**
+ * Whether this TaskFragment contains activities of another process/package.
+ */
+ private boolean mHasCrossProcessActivities;
+
+ /**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
@@ -418,10 +423,18 @@ class TaskFragmentContainer {
mAppearEmptyTimeout = null;
}
+ mHasCrossProcessActivities = false;
mInfo = info;
if (mInfo == null || mInfo.isEmpty()) {
return;
}
+
+ // Contains activities of another process if the activities size is not matched to the
+ // running activity count
+ if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
+ mHasCrossProcessActivities = true;
+ }
+
// Only track the pending Intent when the container is empty.
mPendingAppearedIntent = null;
if (mPendingAppearedActivities.isEmpty()) {
@@ -751,6 +764,11 @@ class TaskFragmentContainer {
return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
}
+ /** Whether contains activities of another process */
+ boolean hasCrossProcessActivities() {
+ return mHasCrossProcessActivities;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
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 8386131b177d..a7a6b3c92157 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -122,16 +122,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
addWindowLayoutInfoListener(activity, extConsumer);
}
- @Override
- public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
- @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
- final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
- synchronized (mLock) {
- mJavaToExtConsumers.put(consumer, extConsumer);
- }
- addWindowLayoutInfoListener(context, extConsumer);
- }
-
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
* takes a UI Context as a parameter.
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 3d0e1c8cf788..ff08782e8cd8 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
@@ -565,7 +565,6 @@ public class SplitControllerTest {
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
assertTrue(container.areLastRequestedBoundsEqual(null));
- assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
}
@Test
@@ -1008,9 +1007,27 @@ public class SplitControllerTest {
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
- assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
- mSplitController.getContainerWithActivity(mActivity));
- verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
+ assertTrue(mSplitController.getContainerWithActivity(mActivity)
+ .areLastRequestedBoundsEqual(new Rect()));
+ }
+
+ @Test
+ public void testFindActivityBelow() {
+ // Create a container with two activities
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pendingAppearedActivity = createMockActivity();
+ container.addPendingAppearedActivity(pendingAppearedActivity);
+
+ // Ensure the activity below matches
+ assertEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
+
+ // Ensure that the activity look up won't search for the in-process activities and should
+ // IPC to WM core to get the activity below. It should be `null` for this mock test.
+ spyOn(container);
+ doReturn(true).when(container).hasCrossProcessActivities();
+ assertNotEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
}
@Test
@@ -1196,7 +1213,7 @@ public class SplitControllerTest {
.build();
assertTrue("Rules must have same presentation if tags are null and has same properties.",
- SplitController.haveSamePresentation(splitRule1, splitRule2,
+ SplitController.areRulesSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
splitRule2 = createSplitPairRuleBuilder(
@@ -1211,7 +1228,7 @@ public class SplitControllerTest {
assertFalse("Rules must have different presentations if tags are not equal regardless"
+ "of other properties",
- SplitController.haveSamePresentation(splitRule1, splitRule2,
+ SplitController.areRulesSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 83301564d7a4..6981d9d7ebb8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -678,7 +678,8 @@ public class SplitPresenterTest {
.setShouldClearTop(false)
.build();
- mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule);
+ mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule,
+ SPLIT_ATTRIBUTES);
assertEquals(primaryTf, mController.getContainerWithActivity(mActivity));
final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity(
@@ -729,6 +730,27 @@ public class SplitPresenterTest {
}
@Test
+ public void testComputeSplitAttributesOnHingeSplitTypeOnDeviceWithoutFoldingFeature() {
+ final SplitAttributes hingeSplitAttrs = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()))
+ .build();
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(hingeSplitAttrs)
+ .build();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
+ doReturn(null).when(mPresenter).getFoldingFeature(any());
+
+ assertEquals(hingeSplitAttrs, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, hingeSplitAttrs, null /* minDimensionsPair */));
+ }
+
+ @Test
public void testGetTaskWindowMetrics() {
final Configuration taskConfig = new Configuration();
taskConfig.windowConfiguration.setBounds(TASK_BOUNDS);
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 96ff840b984b..000000000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
deleted file mode 100644
index c3b6916121d0..000000000000
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c7c94246b96a..54978bd4496d 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,6 +46,8 @@ filegroup {
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/common/bubbles/*.java",
+ "src/com/android/wm/shell/common/TriangleShape.java",
"src/com/android/wm/shell/animation/Interpolators.java",
"src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4b125904004a..f0ed6ee5cb67 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
diff --git a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
index 9473cdd607d6..01df006f1bd2 100644
--- a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
@@ -16,5 +16,5 @@
-->
<!-- Should be the same as in packages/apps/Launcher3/res/color-night-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="20" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
index 0e165fca4fd3..876ee02a8adf 100644
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -16,5 +16,5 @@
-->
<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="95" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml
new file mode 100644
index 000000000000..e33eb126012d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="5" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
deleted file mode 100644
index 8779cc09715b..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
deleted file mode 100644
index ea0fbb0e5d33..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
deleted file mode 100644
index c55cbe2d054c..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
deleted file mode 100644
index 447df43dfddd..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
deleted file mode 100644
index 8c60c8407174..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group
- android:translateX="4.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M13.7021 12.5833L16.5676 15.5L15.426 16.7333L12.526 13.8333L10.4426 15.9167V10.5H15.9176L13.7021 12.5833ZM13.8343 3.83333H15.501V5.5H13.8343V3.83333ZM15.501 2.16667H13.8343V0.566667C14.751 0.566667 15.501 1.33333 15.501 2.16667ZM10.501 0.5H12.1676V2.16667H10.501V0.5ZM13.8343 7.16667H15.501V8.83333H13.8343V7.16667ZM5.50098 15.5H3.83431V13.8333H5.50098V15.5ZM2.16764 5.5H0.500977V3.83333H2.16764V5.5ZM2.16764 0.566667V2.16667H0.500977C0.500977 1.33333 1.33431 0.566667 2.16764 0.566667ZM2.16764 12.1667H0.500977V10.5H2.16764V12.1667ZM5.50098 2.16667H3.83431V0.5H5.50098V2.16667ZM8.83431 2.16667H7.16764V0.5H8.83431V2.16667ZM8.83431 15.5H7.16764V13.8333H8.83431V15.5ZM2.16764 8.83333H0.500977V7.16667H2.16764V8.83333ZM2.16764 15.5667C1.25098 15.5667 0.500977 14.6667 0.500977 13.8333H2.16764V15.5667Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
deleted file mode 100644
index c334a543a86a..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:translateX="6.0"
- android:translateY="8.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 27e0b184f427..5d7771366bec 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -14,13 +14,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@color/decor_button_dark_color">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<group android:translateY="8.0">
<path
- android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index c6e634c6622c..4ee10f429b37 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
@@ -17,6 +17,5 @@
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
- <corners android:radius="@dimen/caption_menu_corner_radius" />
- <stroke android:width="1dp" android:color="#b3b3b3"/>
+ <corners android:radius="@dimen/desktop_mode_handle_menu_corner_radius" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml
index 166552dcb9e8..b7521d4200c0 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
->
- <group android:scaleX="1.25"
- android:scaleY="1.75"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M10.3937 6.93935L11.3337 5.99935L6.00033 0.666016L0.666992 5.99935L1.60699 6.93935L6.00033 2.55268"/>
- </group>
+ android:height="20dp"
+ android:tint="#000000"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:width="20dp">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml
new file mode 100644
index 000000000000..e2b724b8abfd
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M16.667,15H3.333V5H16.667V15ZM16.667,16.667C17.583,16.667 18.333,15.917 18.333,15V5C18.333,4.083 17.583,3.333 16.667,3.333H3.333C2.417,3.333 1.667,4.083 1.667,5V15C1.667,15.917 2.417,16.667 3.333,16.667H16.667ZM15,6.667H9.167V8.333H13.333V10H15V6.667ZM5,9.167H12.5V13.333H5V9.167Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml
index e258564c70f7..b0ea98e5f788 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateY="4.0">
- <path
- android:fillColor="#FFFF0000"
- android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
- </group>
+ android:width="21dp"
+ android:height="20dp"
+ android:viewportWidth="21"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M3.667,15H17V5H3.667V15ZM18.667,15C18.667,15.917 17.917,16.667 17,16.667H3.667C2.75,16.667 2,15.917 2,15V5C2,4.083 2.75,3.333 3.667,3.333H17C17.917,3.333 18.667,4.083 18.667,5V15ZM11.167,6.667H15.333V11.667H11.167V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml
index 7c86888f5226..99e1d268c97c 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateY="4.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M10,38V28.35H13V35H19.65V38ZM10,19.65V10H19.65V13H13V19.65ZM28.35,38V35H35V28.35H38V38ZM35,19.65V13H28.35V10H38V19.65Z"/>
- </group>
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M3.333,15H16.667V5H3.333V15ZM18.333,15C18.333,15.917 17.583,16.667 16.667,16.667H3.333C2.417,16.667 1.667,15.917 1.667,15V5C1.667,4.083 2.417,3.333 3.333,3.333H16.667C17.583,3.333 18.333,4.083 18.333,5V15ZM5,6.667H15V13.333H5V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml
new file mode 100644
index 000000000000..79a91250bb78
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M18.333,5.833L18.333,8.333L16.667,8.333L16.667,5.833L13.333,5.833L13.333,4.167L16.667,4.167C17.587,4.167 18.333,4.913 18.333,5.833Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M6.667,4.167L3.333,4.167C2.413,4.167 1.667,4.913 1.667,5.833L1.667,8.333L3.333,8.333L3.333,5.833L6.667,5.833L6.667,4.167Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M6.667,14.167L3.333,14.167L3.333,11.667L1.667,11.667L1.667,14.167C1.667,15.087 2.413,15.833 3.333,15.833L6.667,15.833L6.667,14.167Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M13.333,15.833L16.667,15.833C17.587,15.833 18.333,15.087 18.333,14.167L18.333,11.667L16.667,11.667L16.667,14.167L13.333,14.167L13.333,15.833Z"
+ android:fillColor="#1C1C14"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
new file mode 100644
index 000000000000..7c4f49979455
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
+ android:fillColor="#1C1C14"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml
new file mode 100644
index 000000000000..853ab60e046f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="20dp"
+ android:viewportWidth="21"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M17.333,15H4V5H17.333V15ZM17.333,16.667C18.25,16.667 19,15.917 19,15V5C19,4.083 18.25,3.333 17.333,3.333H4C3.083,3.333 2.333,4.083 2.333,5V15C2.333,15.917 3.083,16.667 4,16.667H17.333ZM9.833,6.667H5.667V13.333H9.833V6.667ZM11.5,6.667H15.667V13.333H11.5V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
new file mode 100644
index 000000000000..022594982ca3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#bf309fb5" />
+ <corners android:radius="20dp" />
+ <stroke android:width="1dp" color="#A00080FF"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
new file mode 100644
index 000000000000..3e0297ab612b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector android:height="24dp" android:tint="#000000"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
new file mode 100644
index 000000000000..029d83881165
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <group android:scaleX="-1" android:translateX="960">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+ </group>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
new file mode 100644
index 000000000000..592f899d2ecc
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
index 2dba37daf059..4c28e519afa7 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/tv_window_menu_button_radius" />
- <solid android:color="@color/tv_window_menu_icon_bg" />
+ <solid android:color="@android:color/white" />
</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
new file mode 100644
index 000000000000..681a52bea2b2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:id="@+id/bubble_bar_expanded_view">
+
+</com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
new file mode 100644
index 000000000000..fb1980a52601
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/desktop_mode_caption"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_title">
+
+ <LinearLayout
+ android:id="@+id/open_menu_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingStart="8dp">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_margin="4dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/app_icon_text" />
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:minWidth="80dp"
+ android:textColor="@color/desktop_mode_caption_app_name_dark"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:gravity="center_vertical"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ tools:text="Gmail"/>
+
+ <ImageButton
+ android:id="@+id/expand_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:contentDescription="@string/expand_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:tint="@color/desktop_mode_caption_expand_button_dark"
+ android:background="@null"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ android:focusable="false"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/caption_handle"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/close_window"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:padding="4dp"
+ android:layout_marginEnd="8dp"
+ android:contentDescription="@string/close_button_text"
+ android:src="@drawable/decor_close_button_dark"
+ android:scaleType="fitCenter"
+ android:gravity="end"
+ android:background="@null"
+ android:tint="@color/desktop_mode_caption_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
deleted file mode 100644
index f9aeb6a8448a..000000000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/handle_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="middle"
- android:dividerPadding="18dip">
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_margin="12dp"
- android:contentDescription="@string/app_icon_text"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"/>
- <TextView
- android:id="@+id/application_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@+id/application_icon"
- android:layout_toStartOf="@+id/collapse_menu_button"
- android:textColor="#FF000000"
- android:layout_centerVertical="true"/>
- <Button
- android:id="@+id/collapse_menu_button"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginEnd="10dp"
- android:contentDescription="@string/collapse_menu_text"
- android:layout_alignParentEnd="true"
- android:background="@drawable/caption_collapse_menu_button"
- android:layout_centerVertical="true"/>
- </RelativeLayout>
- <LinearLayout
- android:id="@+id/windowing_mode_buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal">
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="0.5" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/fullscreen_button"
- android:contentDescription="@string/fullscreen_text"
- android:src="@drawable/caption_fullscreen_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/split_screen_button"
- android:contentDescription="@string/split_screen_text"
- android:src="@drawable/caption_split_screen_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/floating_button"
- android:contentDescription="@string/float_button_text"
- android:src="@drawable/caption_floating_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/desktop_button"
- android:contentDescription="@string/desktop_text"
- android:src="@drawable/caption_desktop_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="0.5" />
-
- </LinearLayout>
- <LinearLayout
- android:id="@+id/menu_buttons_misc"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/screenshot_button"
- android:contentDescription="@string/screenshot_text"
- android:text="@string/screenshot_text"
- android:drawableStart="@drawable/caption_screenshot_button"/>
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/caption_select_button"/>
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/close_button"
- android:contentDescription="@string/close_text"
- android:text="@string/close_text"
- android:drawableStart="@drawable/caption_close_button"
- android:textColor="#FFFF0000"/>
- </LinearLayout>
-</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 29cf1512e2e5..1d6864c152c2 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -16,26 +16,22 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/desktop_mode_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@drawable/desktop_mode_decor_title">
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/back_button"
- android:contentDescription="@string/back_button_text"
- android:background="@drawable/decor_back_button_dark"/>
- <Button
+
+ <ImageButton
android:id="@+id/caption_handle"
- android:layout_width="128dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
+ android:layout_width="176dp"
+ android:layout_height="42dp"
+ android:paddingHorizontal="24dp"
android:contentDescription="@string/handle_text"
- android:background="@drawable/decor_handle_dark"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/close_window"
- android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"/>
+ android:src="@drawable/decor_handle_dark"
+ tools:tint="@color/desktop_mode_caption_handle_bar_dark"
+ android:scaleType="fitXY"
+ android:background="?android:selectableItemBackground"/>
+
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
new file mode 100644
index 000000000000..167a003932d6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="14dp"
+ android:layout_marginEnd="14dp"
+ android:contentDescription="@string/app_icon_text"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ tools:text="Gmail"
+ android:textColor="@color/desktop_mode_caption_menu_text_color"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:lineHeight="20dp"
+ android:textStyle="normal"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/collapse_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:layout_marginEnd="14dp"
+ android:layout_marginStart="14dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:rotation="180"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:background="?android:selectableItemBackgroundBorderless"/>
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
new file mode 100644
index 000000000000..40a4b53f3e1d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
+ android:orientation="vertical"
+ android:background="@drawable/desktop_mode_decor_menu_background">
+
+ <Button
+ android:id="@+id/screenshot_button"
+ android:contentDescription="@string/screenshot_text"
+ android:text="@string/screenshot_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/select_button"
+ android:contentDescription="@string/select_text"
+ android:text="@string/select_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/close_button"
+ android:contentDescription="@string/close_text"
+ android:text="@string/close_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_close"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
new file mode 100644
index 000000000000..95283b9e214a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageButton
+ android:id="@+id/fullscreen_button"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/fullscreen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/split_screen_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/split_screen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/floating_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/float_button_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_floating"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/desktop_button"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/desktop_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_active"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
index 413cfd78fd91..a993469aaccf 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout
+<com.android.wm.shell.compatui.LetterboxEduDialogLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/LetterboxDialog">
@@ -78,13 +78,13 @@
android:orientation="horizontal"
android:paddingTop="48dp">
- <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/letterbox_education_ic_reposition"
app:text="@string/letterbox_education_reposition_text"/>
- <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart=
@@ -118,4 +118,4 @@
</FrameLayout>
-</com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout>
+</com.android.wm.shell.compatui.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
new file mode 100644
index 000000000000..49491a7b572c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.compatui.ReachabilityEduLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:focusable="false"
+ android:focusableInTouchMode="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/ReachabilityEduHandLayout"
+ android:text="@string/letterbox_reachability_reposition_text"
+ app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
+ android:layout_gravity="center_horizontal|top"
+ android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
+ android:id="@+id/reachability_move_up_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/ReachabilityEduHandLayout"
+ android:text="@string/letterbox_reachability_reposition_text"
+ app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
+ android:layout_gravity="center_vertical|right"
+ android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
+ android:id="@+id/reachability_move_right_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/ReachabilityEduHandLayout"
+ android:text="@string/letterbox_reachability_reposition_text"
+ app:drawableTopCompat="@drawable/reachability_education_ic_left_hand"
+ android:layout_gravity="center_vertical|left"
+ android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
+ android:id="@+id/reachability_move_left_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/ReachabilityEduHandLayout"
+ android:text="@string/letterbox_reachability_reposition_text"
+ app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
+ android:id="@+id/reachability_move_down_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</com.android.wm.shell.compatui.ReachabilityEduLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index dcce4698c252..ab64f9e359b0 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -67,7 +67,7 @@
<!-- Temporarily extending the background to show an edu text hint for opening the menu -->
<FrameLayout
- android:id="@+id/tv_pip_menu_edu_text_drawer_placeholder"
+ android:id="@+id/tv_pip_menu_edu_text_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tv_pip"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
index 5af40200d240..bd48ad2cef44 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
@@ -19,10 +19,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
+ android:id="@+id/background_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_background"
- android:elevation="@dimen/pip_menu_elevation"/>
+ android:elevation="@dimen/pip_menu_elevation_no_menu"/>
</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 76e9fd538cf3..36619b7d35dc 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Verander grootte"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Hou vas"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"App sal dalk nie met verdeelde skerm werk nie"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App steun nie verdeelde skerm nie"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
- <string name="divider_title" msgid="5482989479865361192">"Skermverdeler"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Skermverdeler"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Skermverdeler"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Volskerm links"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Links 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Beweeg na regs onder"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-instellings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Maak borrel toe"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Moenie borrels wys nie"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Moenie dat gesprek \'n borrel word nie"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klets met borrels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nuwe gesprekke verskyn as swerwende ikone, of borrels Tik op borrel om dit oop te maak. Sleep om dit te skuif."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep ’n ander program in vir verdeelde skerm"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\nhierdie app te skuif"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
<string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skermskoot"</string>
<string name="close_text" msgid="4986518933445178928">"Maak toe"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Maak kieslys oop"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 969af096f571..3d0ec321b6d8 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"መጠን ይቀይሩ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
- <string name="divider_title" msgid="5482989479865361192">"የተከፈለ የማያ ገጽ ከፋይ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገጽ ከፋይ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"የተከፈለ የማያ ገጽ ከፋይ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገጽ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ግራ 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ታችኛውን ቀኝ ያንቀሳቅሱ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"የ<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ቅንብሮች"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"አረፋን አሰናብት"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ዓረፋ አትፍጠር"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገጽ እይታ"</string>
<string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"ምናሌን ክፈት"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 94014bc106bc..e311f66a2553 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"تغيير الحجم"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"إخفاء"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"قد لا يعمل التطبيق بشكل سليم في وضع تقسيم الشاشة."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"لا يعمل التطبيق في وضع تقسيم الشاشة."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
- <string name="divider_title" msgid="5482989479865361192">"أداة تقسيم الشاشة"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"أداة تقسيم الشاشة"</string>
+ <string name="divider_title" msgid="1963391955593749442">"أداة تقسيم الشاشة"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"عرض النافذة اليسرى بملء الشاشة"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ضبط حجم النافذة اليسرى ليكون ٧٠%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ضبط حجم النافذة اليسرى ليكون ٥٠%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نقل إلى أسفل اليسار"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"إعدادات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"إغلاق فقاعة المحادثة"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"عدم إظهار فقاعات المحادثات"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
<string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
<string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
@@ -108,4 +110,6 @@
<string name="screenshot_text" msgid="1477704010087786671">"لقطة شاشة"</string>
<string name="close_text" msgid="4986518933445178928">"إغلاق"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
+ <!-- no translation found for expand_menu_text (3847736164494181168) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 519b5cd2e6cb..8991588c7152 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"আকাৰ সলনি কৰক"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"লুকুৱাওক"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
- <string name="divider_title" msgid="5482989479865361192">"বিভাজিত স্ক্ৰীনৰ বিভাজক"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
+ <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"তলৰ সোঁফালে নিয়ক"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ছেটিং"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল অগ্ৰাহ্য কৰক"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"বাবল কৰাটো বন্ধ কৰক"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"বাৰ্তালাপ বাবল নকৰিব"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"নতুন বাৰ্তালাপ উপঙি থকা চিহ্নসমূহ অথবা bubbles হিচাপে প্ৰদর্শিত হয়। Bubbles খুলিবলৈ টিপক। এইটো স্থানান্তৰ কৰিবলৈ টানি নিয়ক।"</string>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
<string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"স্ক্ৰীনশ্বট"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খোলক"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 5f7db280b6f0..0efa3b53398f 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ölçüsünü dəyişin"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Güvənli məkanda saxlayın"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Tətbiq bölünmüş ekranda işləməyə bilər"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Tətbiq bölünmüş ekranı dəstəkləmir"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
- <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcısı"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcısı"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Bölünmüş ekran ayırıcısı"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Sol tam ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sol 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Aşağıya sağa köçürün"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Yumrucuğu ləğv edin"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Yumrucuqları dayandırın"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Söhbəti yumrucuqda göstərmə"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Yumrucuqlardan istifadə edərək söhbət edin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yeni söhbətlər üzən nişanlar və ya yumrucuqlar kimi görünür. Yumrucuğu açmaq üçün toxunun. Hərəkət etdirmək üçün sürüşdürün."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekrandan istifadə etmək üçün başqa tətbiqi sürüşdürüb gətirin"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran üçün başqa tətbiq sürüşdürün"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tətbiqi köçürmək üçün\niki dəfə toxunun"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
<string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skrinşot"</string>
<string name="close_text" msgid="4986518933445178928">"Bağlayın"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menyunu açın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 0f54e86bc54c..a010a9801526 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promenite veličinu"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavite u tajnu memoriju"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podeljeni ekran."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
- <string name="divider_title" msgid="5482989479865361192">"Razdelnik podeljenog ekrana"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Razdelnik podeljenog ekrana"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Razdelnik podeljenog ekrana"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Režim celog ekrana za levi ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi ekran 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premesti dole desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Podešavanja za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Bez oblačića"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne koristi oblačiće za konverzaciju"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Ćaskajte u oblačićima"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nove konverzacije se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da biste otvorili oblačić. Prevucite da biste ga premestili."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremestili ovu aplikaciju"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otvorite meni"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index bf1113dcb066..e7d22e05937f 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Змяніць памер"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Схаваць"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Праграма можа не працаваць у рэжыме падзеленага экрана"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Праграма не падтрымлівае рэжым падзеленага экрана"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
- <string name="divider_title" msgid="5482989479865361192">"Раздзяляльнік падзеленага экрана"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Раздзяляльнік падзеленага экрана"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Раздзяляльнік падзеленага экрана"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левы экран – поўнаэкранны рэжым"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левы экран – 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левы экран – 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перамясціць правей і ніжэй"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налады \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Адхіліць апавяшчэнне"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Выключыць усплывальныя апавяшчэнні"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Здымак экрана"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыць"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Адкрыць меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 996280e41e53..ad26350bc545 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Преоразмеряване"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Съхраняване"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Приложението може да не работи в режим на разделен екран"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложението не поддържа разделен екран"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
- <string name="divider_title" msgid="5482989479865361192">"Разделител в режима за разделен екран"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Разделител в режима за разделен екран"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Разделител в режима за разделен екран"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ляв екран: Показване на цял екран"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ляв екран: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ляв екран: 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Преместване долу вдясно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Настройки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отхвърляне на балончетата"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Без балончета"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Екранна снимка"</string>
<string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Отваряне на менюто"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 582b9a749cbb..eb23dcdad561 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"রিসাইজ করুন"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"স্ট্যাস করুন"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"স্প্লিট স্ক্রিনে এই অ্যাপ নাও কাজ করতে পারে"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"স্প্লিট স্ক্রিনে এই অ্যাপ কাজ করে না"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
- <string name="divider_title" msgid="5482989479865361192">"স্প্লিট স্ক্রিন বিভাজক"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্রিন বিভাজক"</string>
+ <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্রিন বিভাজক"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাঁ দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"৭০% বাকি আছে"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"৫০% বাকি আছে"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"নিচে ডান দিকে সরান"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> সেটিংস"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল খারিজ করুন"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"বাবল করা বন্ধ করুন"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"স্প্লিট স্ক্রিনের ক্ষেত্রে অন্য কোনও অ্যাপ টেনে আনুন"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"স্ক্রিনশট"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খুলুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 29d4ac4e88c6..1fbfab42a746 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavljanje u stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati na podijeljenom ekranu"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni ekran"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
- <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog ekrana"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog ekrana"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Razdjelnik podijeljenog ekrana"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevo cijeli ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevo 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pomjerite dolje desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke aplikacije <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Zaustavi oblačiće"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nemoj prikazivati razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatajte koristeći oblačiće"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da otvorite oblačić. Prevucite da ga premjestite."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dodirnite dvaput da\npomjerite aplikaciju"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje menija"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index a55d7b7446d2..3f041f4384df 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Canvia la mida"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Amaga"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"És possible que l\'aplicació no funcioni amb la pantalla dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'aplicació no admet la pantalla dividida"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
- <string name="divider_title" msgid="5482989479865361192">"Separador de pantalla dividida"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Separador de pantalla dividida"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Separador de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla esquerra completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pantalla esquerra al 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string>
@@ -58,7 +58,7 @@
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Inicia el mode d\'una mà"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Surt del mode d\'una mà"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configuració de les bombolles: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú addicional"</string>
+ <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú de desbordament"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Torna a afegir a la pila"</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>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>) i <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> més"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mou a baix a la dreta"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuració de l\'aplicació <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora la bombolla"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostris com a bombolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostris la conversa com a bombolla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xateja amb bombolles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les converses noves es mostren com a icones flotants o bombolles. Toca per obrir una bombolla. Arrossega-la per moure-la."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel·la"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Fes doble toc per\nmoure aquesta aplicació"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
<string name="close_text" msgid="4986518933445178928">"Tanca"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Obre el menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 38f911dd1038..c734ef8aef5c 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Změnit velikost"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Uložit"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikace v režimu rozdělené obrazovky nemusí fungovat"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikace nepodporuje režim rozdělené obrazovky"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
- <string name="divider_title" msgid="5482989479865361192">"Čára rozdělující obrazovku"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Čára rozdělující obrazovku"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Čára rozdělující obrazovku"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levá část na celou obrazovku"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % vlevo"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Přesunout vpravo dolů"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavení <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavřít bublinu"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nezobrazovat bubliny"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovat konverzaci v bublinách"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatujte pomocí bublin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nové konverzace se zobrazují jako plovoucí ikony, neboli bubliny. Klepnutím bublinu otevřete. Přetažením ji posunete."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušit"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvojitým klepnutím\npřesunete aplikaci"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Snímek obrazovky"</string>
<string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otevřít nabídku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index ca151289a9d9..036bdc109680 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Rediger størrelse"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skjul"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Appen fungerer muligvis ikke i opdelt skærm"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen understøtter ikke opdelt skærm"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
- <string name="divider_title" msgid="5482989479865361192">"Adskiller til opdelt skærm"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Adskiller til opdelt skærm"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Adskiller til opdelt skærm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vis venstre del i fuld skærm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Venstre 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flyt ned til højre"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Stop med at vise bobler"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat ved hjælp af bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Træk en anden app hertil for at bruge opdelt skærm"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Træk en anden app hertil for at bruge opdelt skærm"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuller"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryk to gange\nfor at flytte appen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Luk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Åbn menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 916fa85f74e7..ec1b9d3a1734 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Größe anpassen"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"In Stash legen"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert bei geteiltem Bildschirm unter Umständen nicht"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"„Geteilter Bildschirm“ wird in dieser App nicht unterstützt"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
- <string name="divider_title" msgid="5482989479865361192">"Bildschirmteiler"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Bildschirmteiler"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Bildschirmteiler"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vollbild links"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % links"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Einstellungen für <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Keine Bubbles zulassen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Unterhaltung nicht als Bubble anzeigen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles zum Chatten verwenden"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Neue Unterhaltungen erscheinen als unverankerte Symbole, „Bubbles“ genannt. Wenn du eine Bubble öffnen möchtest, tippe sie an. Wenn du sie verschieben möchtest, zieh an ihr."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Abbrechen"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Zum Verschieben\ndoppeltippen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
<string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
@@ -108,4 +110,6 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Schließen"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
+ <!-- no translation found for expand_menu_text (3847736164494181168) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 58d19f77c15e..11f9dd1c0810 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Αλλαγή μεγέθους"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Απόκρυψη"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
- <string name="divider_title" msgid="5482989479865361192">"Διαχωριστικό οθόνης"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Διαχωριστικό οθόνης"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Διαχωριστικό οθόνης"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Αριστερή πλήρης οθόνη"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Αριστερή 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Αριστερή 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Μετακίνηση κάτω δεξιά"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ρυθμίσεις <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Παράβλ. για συννεφ."</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Να μην εμφανίζει συννεφάκια"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης."</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
<string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Στιγμιότυπο οθόνης"</string>
<string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Άνοιγμα μενού"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index d5da5c6b067d..216d0c139e15 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
- <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index c1f1549a7630..e3795cc15048 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
- <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Open Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index d5da5c6b067d..216d0c139e15 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
- <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index d5da5c6b067d..216d0c139e15 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
- <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 5968df5d9035..b762b80b45cf 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎Resize‎‏‎‎‏‎"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎Stash‎‏‎‎‏‎"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎App may not work with split-screen.‎‏‎‎‏‎"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎App does not support split-screen.‎‏‎‎‏‎"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎App may not work with split screen‎‏‎‎‏‎"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎App does not support split screen‎‏‎‎‏‎"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎This app can only be opened in 1 window.‎‏‎‎‏‎"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎App does not support launch on secondary displays.‎‏‎‎‏‎"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Split-screen divider‎‏‎‎‏‎"</string>
- <string name="divider_title" msgid="5482989479865361192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎Split-screen divider‎‏‎‎‏‎"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎Split screen divider‎‏‎‎‏‎"</string>
+ <string name="divider_title" msgid="1963391955593749442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎Split screen divider‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎Left full screen‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎Left 70%‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎Left 50%‎‏‎‎‏‎"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎Move bottom right‎‏‎‎‏‎"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ settings‎‏‎‎‏‎"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎Dismiss bubble‎‏‎‎‏‎"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎Don’t bubble‎‏‎‎‏‎"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎Don’t bubble conversation‎‏‎‎‏‎"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎Chat using bubbles‎‏‎‎‏‎"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it.‎‏‎‎‏‎"</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎Didn’t fix it?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Tap to revert‎‏‎‎‏‎"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎No camera issues? Tap to dismiss.‎‏‎‎‏‎"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎See and do more‎‏‎‎‏‎"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎Drag in another app for split-screen‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎Drag in another app for split screen‎‏‎‎‏‎"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎Double-tap outside an app to reposition it‎‏‎‎‏‎"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎Cancel‎‏‎‎‏‎"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎Restart‎‏‎‎‏‎"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎Don’t show again‎‏‎‎‏‎"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‏‏‎‎‎Double-tap to‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎move this app‎‏‎‎‏‎"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
<string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
<string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎Screenshot‎‏‎‎‏‎"</string>
<string name="close_text" msgid="4986518933445178928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎Close‎‏‎‎‏‎"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎Close Menu‎‏‎‎‏‎"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎Open Menu‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index e9b052242ba6..628cf7f17f6f 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar el tamaño"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Almacenar de manera segura"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Es posible que la app no funcione en el modo de pantalla dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La app no es compatible con la función de pantalla dividida"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ubicar abajo a la derecha"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Descartar burbuja"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostrar burbujas"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbuja"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat con burbujas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como elementos flotantes o burbujas. Presiona para abrir la burbuja. Arrástrala para moverla."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra app para el modo de pantalla dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra app para el modo de pantalla dividida"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Presiona dos veces\npara mover esta app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Abrir el menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 2c32e710e8ff..cfa7865a599d 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Puede que la aplicación no funcione con la pantalla dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La aplicación no es compatible con la pantalla dividida"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abajo a la derecha"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ajustes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostrar burbujas"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatea con burbujas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamados \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra aplicación para activar la pantalla dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra aplicación para activar la pantalla dividida"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dos veces para\nmover esta aplicación"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 6ba77c3ac0e2..666aa46fbfda 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Suuruse muutmine"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Pane hoidlasse"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Rakendus ei pruugi jagatud ekraanikuvaga töötada."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Rakendus ei toeta jagatud ekraanikuva."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
- <string name="divider_title" msgid="5482989479865361192">"Ekraanijagaja"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Jagatud ekraanikuva jaotur"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Jagatud ekraanikuva jaotur"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasak täisekraan"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasak: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Teisalda alla paremale"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Rakenduse <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> seaded"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Sule mull"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ära kuva mulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ära kuva vestlust mullina"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Vestelge mullide abil"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Uued vestlused kuvatakse hõljuvate ikoonidena ehk mullidena. Puudutage mulli avamiseks. Lohistage mulli, et seda liigutada."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Rakenduse teisaldamiseks\ntopeltpuudutage"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Ekraanipilt"</string>
<string name="close_text" msgid="4986518933445178928">"Sule"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Ava menüü"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index ef11e4703d3d..d7397f312d41 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Aldatu tamaina"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Gorde"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikazioak ez du onartzen pantaila zatitua"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
- <string name="divider_title" msgid="5482989479865361192">"Pantaila-zatitzailea"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Pantaila-zatitzailea"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ezarri ezkerraldea % 70en"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Eraman behealdera, eskuinetara"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aplikazioaren ezarpenak"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baztertu burbuila"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ez erakutsi burbuilarik"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ez erakutsi elkarrizketak burbuila gisa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Txateatu burbuilen bidez"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Elkarrizketa berriak ikono gainerakor edo burbuila gisa agertzen dira. Sakatu burbuila irekitzeko. Arrasta ezazu mugitzeko."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Pantaila zatituta ikusteko, arrastatu beste aplikazio bat"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Pantaila zatitua ikusteko, arrastatu beste aplikazio bat"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Sakatu birritan\naplikazioa mugitzeko"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
<string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Pantaila-argazkia"</string>
<string name="close_text" msgid="4986518933445178928">"Itxi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Ireki menua"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index b0cfd32d2515..5e13c3e9bdbf 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"تغییر اندازه"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"مخفی‌سازی"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفی‌سازی"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمی‌کند."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن است برنامه با صفحهٔ دونیمه کار نکند"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"برنامه از صفحهٔ دونیمه پشتیبانی نمی‌کند"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره می‌تواند باز شود."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راه‌اندازی در نمایشگرهای ثانویه پشتیبانی نمی‌کند."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"تقسیم‌کننده صفحه"</string>
- <string name="divider_title" msgid="5482989479865361192">"تقسیم‌کننده صفحهٔ دونیمه"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"تقسیم‌کننده صفحهٔ دونیمه"</string>
+ <string name="divider_title" msgid="1963391955593749442">"تقسیم‌کننده صفحهٔ دونیمه"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"تمام‌صفحه چپ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"٪۷۰ چپ"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"٪۵۰ چپ"</string>
@@ -58,7 +58,7 @@
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یک‌دستی»"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یک‌دستی»"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابک‌های <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"لبریزشده"</string>
+ <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"انتقال به پایین سمت چپ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"تنظیمات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"رد کردن حبابک"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"حبابک نشان داده نشود"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
<string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"نماگرفت"</string>
<string name="close_text" msgid="4986518933445178928">"بستن"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"باز کردن منو"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 90a196bcd46e..c047c656f19a 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Muuta kokoa"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Lisää turvasäilytykseen"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Sovellus ei ehkä toimi jaetulla näytöllä"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Sovellus ei tue jaetun näytön tilaa"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
- <string name="divider_title" msgid="5482989479865361192">"Näytönjakaja"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Näytönjakaja"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Näytönjakaja"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasen koko näytölle"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasen 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Siirrä oikeaan alareunaan"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: asetukset"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ohita kupla"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Älä näytä puhekuplia"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Älä näytä kuplia keskusteluista"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chattaile kuplien avulla"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Uudet keskustelut näkyvät kelluvina kuvakkeina tai kuplina. Avaa kupla napauttamalla. Siirrä sitä vetämällä."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Peru"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Kaksoisnapauta, jos\nhaluat siirtää sovellusta"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Kuvakaappaus"</string>
<string name="close_text" msgid="4986518933445178928">"Sulje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Avaa valikko"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index bc82f5123585..6b60fdca8a58 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -32,13 +32,13 @@
<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="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</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="5242868470666346929">"Cette application ne peut être ouverte que dans une 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="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
- <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</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>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % à la gauche"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne pas afficher de bulles"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
<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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Faites glisser une autre application pour utiliser l\'écran partagé"</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_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
@@ -93,6 +94,7 @@
<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="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>
@@ -108,4 +110,6 @@
<string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
+ <!-- no translation found for expand_menu_text (3847736164494181168) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 61b7fad839ab..768936e27073 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner en mode Écran partagé"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appli incompatible avec l\'écran partagé"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 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="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
- <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</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">"Écran de gauche en plein écran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Écran de gauche à 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Désactiver les info-bulles"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatter en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou de bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre appli pour utiliser l\'écran partagé"</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">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
@@ -93,6 +94,7 @@
<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">"Appuyez deux fois\npour dé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>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 482519897401..ad5629a1f6cf 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"É posible que a aplicación non funcione coa pantalla dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A aplicación non admite a función de pantalla dividida"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla completa á esquerda"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % á esquerda"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover á parte inferior dereita"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar burbulla"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Non mostrar burbullas"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mostrar a conversa como burbulla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatear usando burbullas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"As conversas novas aparecen como iconas flotantes ou burbullas. Toca para abrir a burbulla e arrastra para movela."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra outra aplicación para usar a pantalla dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra outra aplicación para usar a pantalla dividida"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dúas veces para\nmover esta aplicación"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
@@ -108,4 +110,6 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
<string name="close_text" msgid="4986518933445178928">"Pechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
+ <!-- no translation found for expand_menu_text (3847736164494181168) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index c1eedf8dbf7d..29e044fa838e 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"કદ બદલો"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"છુપાવો"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"વિભાજિત સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ઍપ વિભાજિત સ્ક્રીનને સપોર્ટ કરતી નથી"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
- <string name="divider_title" msgid="5482989479865361192">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
+ <string name="divider_title" msgid="1963391955593749442">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ડાબી પૂર્ણ સ્ક્રીન"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ડાબે 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ડાબે 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"નીચે જમણે ખસેડો"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> સેટિંગ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોડી દો"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"બબલ બતાવશો નહીં"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"વિભાજિત સ્ક્રીન માટે કોઈ અન્ય ઍપમાં ખેંચો"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
<string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
<string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"સ્ક્રીનશૉટ"</string>
<string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"મેનૂ ખોલો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index a12a779c78a1..1934aa598ad2 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदलें"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"छिपाएं"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्‍क्रीन का समर्थन नहीं करता है."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"मुमकिन है कि ऐप्लिकेशन, स्प्लिट स्क्रीन मोड में काम न करे"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यह ऐप्लिकेशन, स्प्लिट स्क्रीन मोड पर काम नहीं करता"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
- <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रीन डिवाइडर"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
+ <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बाईं स्क्रीन को 70% बनाएं"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बाईं स्क्रीन को 50% बनाएं"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"सबसे नीचे दाईं ओर ले जाएं"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> की सेटिंग"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारिज करें"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल होने से रोकें"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन का इस्तेमाल करने के लिए, किसी अन्य ऐप्लिकेशन को खींचें और छोड़ें"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
<string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
<string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"मेन्यू खोलें"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index 595435bcf8b8..e2f272c36329 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"पिक्चर में पिक्चर"</string>
- <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
+ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई टाइटल कार्यक्रम नहीं)"</string>
<string name="pip_close" msgid="2955969519031223530">"बंद करें"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्‍क्रीन"</string>
<string name="pip_move" msgid="158770205886688553">"ले जाएं"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 3ce055e0b879..04a70730fe88 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sakrijte"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni zaslon"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
- <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog zaslona"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog zaslona"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Razdjelnik podijeljenog zaslona"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevi zaslon u cijeli zaslon"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevi zaslon na 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premjestite u donji desni kut"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne prikazuj oblačiće"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Zaustavi razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Oblačići u chatu"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori pojavljuju se kao pomične ikone ili oblačići. Dodirnite za otvaranje oblačića. Povucite da biste ga premjestili."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremjestili ovu aplikaciju"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Snimka zaslona"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje izbornika"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index dc916610d3fc..045af2f015f8 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Átméretezés"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Félretevés"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Az alkalmazás nem támogatja az osztott képernyőt"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
- <string name="divider_title" msgid="5482989479865361192">"Elválasztó az osztott nézetben"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Elválasztó az osztott képernyős nézetben"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Elválasztó az osztott képernyős nézetben"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Bal oldali teljes képernyőre"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Bal oldali 70%-ra"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Áthelyezés le és jobbra"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> beállításai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Buborék elvetése"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne jelenjen meg buborékban"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne jelenjen meg a beszélgetés buborékban"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Buborékokat használó csevegés"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Az új beszélgetések lebegő ikonként, vagyis buborékként jelennek meg. A buborék megnyitásához koppintson rá. Áthelyezéshez húzza a kívánt helyre."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Koppintson duplán\naz alkalmazás áthelyezéséhez"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
<string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Képernyőkép"</string>
<string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menü megnyitása"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 5c58634181f5..30499a4f60ee 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Փոխել չափը"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Թաքցնել"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Հավելվածը չի աջակցում էկրանի տրոհումը"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
- <string name="divider_title" msgid="5482989479865361192">"Տրոհված էկրանի բաժանիչ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Տրոհված էկրանի բաժանիչ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Տրոհված էկրանի բաժանիչ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ձախ էկրանը՝ լիաէկրան"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ձախ էկրանը՝ 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ձախ էկրանը՝ 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Տեղափոխել ներքև՝ աջ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – կարգավորումներ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Փակել ամպիկը"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ցույց չտալ ամպիկները"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
<string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Սքրինշոթ"</string>
<string name="close_text" msgid="4986518933445178928">"Փակել"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Բացել ընտրացանկը"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index c863c7099c29..9c8b6147a780 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah ukuran"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikasi mungkin tidak berfungsi dengan layar terpisah"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikasi tidak mendukung layar terpisah"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
- <string name="divider_title" msgid="5482989479865361192">"Pembagi layar terpisah"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Pembagi layar terpisah"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Pembagi layar terpisah"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Layar penuh di kiri"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pindahkan ke kanan bawah"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setelan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Tutup balon"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Berhenti menampilkan balon"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan gunakan percakapan balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat dalam tampilan balon"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Percakapan baru muncul sebagai ikon mengambang, atau balon. Ketuk untuk membuka balon. Tarik untuk memindahkannya."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketuk dua kali untuk\nmemindahkan aplikasi ini"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 9f1936ccbd37..1507fd60e775 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Breyta stærð"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Geymsla"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Forritið virkar hugsanlega ekki með skjáskiptingu"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Forritið styður ekki skjáskiptingu"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
- <string name="divider_title" msgid="5482989479865361192">"Skjáskipting"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Skilrúm skjáskiptingar"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Skilrúm skjáskiptingar"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vinstri á öllum skjánum"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vinstri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Færðu neðst til hægri"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Stillingar <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Loka blöðru"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ekki sýna blöðrur"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ekki setja samtal í blöðru"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Spjalla með blöðrum"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Ný samtöl birtast sem fljótandi tákn eða blöðrur. Ýttu til að opna blöðru. Dragðu hana til að færa."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Hætta við"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ýttu tvisvar til\nað færa þetta forrit"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
<string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skjámynd"</string>
<string name="close_text" msgid="4986518933445178928">"Loka"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Opna valmynd"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 2a955d58a76f..cd99724d3ae4 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ridimensiona"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Accantona"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'app potrebbe non funzionare con lo schermo diviso"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'app non supporta la modalità schermo diviso"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
- <string name="divider_title" msgid="5482989479865361192">"Strumento per schermo diviso"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Strumento per schermo diviso"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Strumento per schermo diviso"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Schermata sinistra a schermo intero"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Schermata sinistra al 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sposta in basso a destra"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Impostazioni <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora bolla"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Non mostrare i fumetti"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mettere la conversazione nella bolla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatta utilizzando le bolle"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Le nuove conversazioni vengono mostrate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trascina in un\'altra app per usare lo schermo diviso"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tocca due volte per\nspostare questa app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
<string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Chiudi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Apri menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index b1e71514bd60..21bee9ff0df5 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"שינוי גודל"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"הסתרה זמנית"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"יכול להיות שהאפליקציה לא תפעל עם מסך מפוצל"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"האפליקציה לא תומכת במסך מפוצל"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
- <string name="divider_title" msgid="5482989479865361192">"מחלק מסך מפוצל"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"מחלק מסך מפוצל"</string>
+ <string name="divider_title" msgid="1963391955593749442">"מחלק מסך מפוצל"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"מסך שמאלי מלא"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"שמאלה 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"שמאלה 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"העברה לפינה הימנית התחתונה"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"הגדרות <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"סגירת בועה"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ללא בועות"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
<string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"צילום מסך"</string>
<string name="close_text" msgid="4986518933445178928">"סגירה"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"פתיחת התפריט"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index a765d8c28c6c..9451dbbfa4f6 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"サイズ変更"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"非表示"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"アプリは分割画面では動作しないことがあります"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"アプリで分割画面がサポートされていません"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
- <string name="divider_title" msgid="5482989479865361192">"分割画面の分割線"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"分割画面の分割線"</string>
+ <string name="divider_title" msgid="1963391955593749442">"分割画面の分割線"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左全画面"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"右下に移動"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> の設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"バブルを閉じる"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"バブルで表示しない"</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>
@@ -84,15 +85,16 @@
<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="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string>
- <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"アプリを再起動して画面表示を最適化しますか?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動することにより表示を最適化できますが、保存されていない変更は失われる可能性があります"</string>
<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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"スクリーンショット"</string>
<string name="close_text" msgid="4986518933445178928">"閉じる"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"メニューを開く"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 51f05225dd53..ab5c83991940 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ზომის შეცვლა"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"გადანახვა"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
- <string name="divider_title" msgid="5482989479865361192">"ეკრანის გაყოფის გამყოფი"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"ეკრანის გაყოფის გამყოფი"</string>
+ <string name="divider_title" msgid="1963391955593749442">"ეკრანის გაყოფის გამყოფი"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"მარცხენა ნაწილის სრულ ეკრანზე გაშლა"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"მარცხენა ეკრანი — 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"მარცხენა ეკრანი — 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"გადაანაცვ. ქვემოთ და მარჯვნივ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-ის პარამეტრები"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ბუშტის დახურვა"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ბუშტის გამორთვა"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
<string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ეკრანის ანაბეჭდი"</string>
<string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"მენიუს გახსნა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 4fe2604ca230..eb37b55d35b3 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Өлшемін өзгерту"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Жасыру"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Қолданбада экранды бөлу мүмкін емес."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
- <string name="divider_title" msgid="5482989479865361192">"Бөлінген экран бөлгіші"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлу режимінің бөлгіші"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Экранды бөлу режимінің бөлгіші"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жағын толық экранға шығару"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% сол жақта"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% сол жақта"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төменгі оң жаққа жылжыту"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлері"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Қалқымалы хабарды жабу"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Қалқыма хабарлар көрсетпеу"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлу үшін басқа қолданбаға өтіңіз."</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
<string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
<string name="close_text" msgid="4986518933445178928">"Жабу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Мәзірді ашу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index f590055da0ad..b8e7ba1c6bb1 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ប្ដូរ​ទំហំ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"លាក់ជាបណ្ដោះអាសន្ន"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធី​អាចនឹងមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ។"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"កម្មវិធី​អាចមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"កម្មវិធីមិនអាចប្រើមុខងារ​បំបែកអេក្រង់បានទេ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធី​នេះមិន​អាច​ចាប់ផ្តើម​នៅលើ​អេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
- <string name="divider_title" msgid="5482989479865361192">"បន្ទាត់ខណ្ឌចែកអេក្រង់បំបែក"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារ​បំបែកអេក្រង់"</string>
+ <string name="divider_title" msgid="1963391955593749442">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារ​បំបែកអេក្រង់"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"អេក្រង់ពេញខាងឆ្វេង"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ឆ្វេង 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ឆ្វេង 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ផ្លាស់ទីទៅផ្នែកខាងក្រោម​ខាងស្ដាំ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ការកំណត់ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ច្រានចោល​ពពុះ"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"កុំបង្ហាញពពុះ"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
<string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
<string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"រូបថតអេក្រង់"</string>
<string name="close_text" msgid="4986518933445178928">"បិទ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"បើកម៉ឺនុយ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 22030eb3dab6..af8dd9b3c200 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವಾಗಿದೆ"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ವೈಶಿಷ್ಟ್ಯ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ಫೀಚರ್ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="pip_play" msgid="3496151081459417097">"ಪ್ಲೇ"</string>
<string name="pip_pause" msgid="690688849510295232">"ವಿರಾಮಗೊಳಿಸಿ"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"ಮುಂದಕ್ಕೆ ಸ್ಕಿಪ್‌ ಮಾಡಿ"</string>
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್‌ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
- <string name="divider_title" msgid="5482989479865361192">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
+ <string name="divider_title" msgid="1963391955593749442">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ಎಡ ಪೂರ್ಣ ಪರದೆ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% ಎಡಕ್ಕೆ"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ಬಬಲ್ ವಜಾಗೊಳಿಸಿ"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ಬಬಲ್ ತೋರಿಸಬೇಡಿ"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್"</string>
<string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"ಮೆನು ತೆರೆಯಿರಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 4f4d0c5f5edf..d9fd59dea513 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"크기 조절"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"숨기기"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"앱이 화면 분할 모드로는 작동하지 않을 수 있습니다"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"앱이 화면 분할을 지원하지 않습니다"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
- <string name="divider_title" msgid="5482989479865361192">"화면 분할기"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"화면 분할기"</string>
+ <string name="divider_title" msgid="1963391955593749442">"화면 분할기"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"왼쪽 화면 전체화면"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"왼쪽 화면 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"왼쪽 화면 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"오른쪽 하단으로 이동"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> 설정"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"대화창 닫기"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"도움말 풍선 중지"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
<string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
<string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"스크린샷"</string>
<string name="close_text" msgid="4986518933445178928">"닫기"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"메뉴 열기"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 4305e9051ebc..543948633cf9 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Өлчөмүн өзгөртүү"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сейфке салуу"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Колдонмодо экран бөлүнбөшү мүмкүн"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Колдонмодо экран бөлүнбөйт"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
- <string name="divider_title" msgid="5482989479865361192">"Экранды бөлгүч"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлгүч"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Экранды бөлгүч"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жактагы экранды толук экран режимине өткөрүү"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Сол жактагы экранды 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Сол жактагы экранды 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Калкып чыкма билдирмелер көрсөтүлбөсүн"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
<string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
<string name="close_text" msgid="4986518933445178928">"Жабуу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Менюну ачуу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 1926b33f9169..fe737176b758 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ປ່ຽນຂະໜາດ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ເກັບໄວ້ບ່ອນເກັບສ່ວນຕົວ"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບໂໝດແບ່ງໜ້າຈໍ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
- <string name="divider_title" msgid="5482989479865361192">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ເຕັມໜ້າຈໍຊ້າຍ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ຊ້າຍ 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ຊ້າຍ 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ຍ້າຍຂວາລຸ່ມ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ການຕັ້ງຄ່າ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ປິດຟອງໄວ້"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ບໍ່ຕ້ອງສະແດງ bubble"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ລາກໄປໄວ້ໃນແອັບອື່ນເພື່ອແບ່ງໜ້າຈໍ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ຮູບໜ້າຈໍ"</string>
<string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"ເປີດເມນູ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index d3dc1b0cab41..37e61a023b64 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Pakeisti dydį"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslėpti"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Programa gali neveikti naudojant išskaidyto ekrano režimą"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programoje nepalaikomas išskaidyto ekrano režimas"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
- <string name="divider_title" msgid="5482989479865361192">"Skaidyto ekrano daliklis"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Išskaidyto ekrano režimo daliklis"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Išskaidyto ekrano režimo daliklis"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kairysis ekranas viso ekrano režimu"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kairysis ekranas 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Perkelti į apačią dešinėje"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ nustatymai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Atsisakyti burbulo"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nerodyti debesėlių"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerodyti pokalbio burbule"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Pokalbis naudojant burbulus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nauji pokalbiai rodomi kaip slankiosios piktogramos arba burbulai. Palieskite, kad atidarytumėte burbulą. Vilkite, kad perkeltumėte."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dukart palieskite, kad\nperkeltumėte šią programą"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
<string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Ekrano kopija"</string>
<string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Atidaryti meniu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 74ec371b481f..58737b23d2f5 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Mainīt lielumu"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslēpt"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Lietotnē netiek atbalstīta ekrāna sadalīšana"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
- <string name="divider_title" msgid="5482989479865361192">"Ekrāna sadalītājs"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Ekrāna sadalītājs"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Ekrāna sadalītājs"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kreisā daļa pa visu ekrānu"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pa kreisi 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pārvietot apakšpusē pa labi"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Lietotnes <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iestatījumi"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Nerādīt burbuli"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Pārtraukt burbuļu rādīšanu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerādīt sarunu burbuļos"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Tērzēšana, izmantojot burbuļus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Jaunas sarunas tiek rādītas kā peldošas ikonas vai burbuļi. Pieskarieties, lai atvērtu burbuli. Velciet, lai to pārvietotu."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Veiciet dubultskārienu,\nlai pārvietotu šo lietotni"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
<string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Ekrānuzņēmums"</string>
<string name="close_text" msgid="4986518933445178928">"Aizvērt"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Atvērt izvēlni"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index a809aa7d030f..f7bcde9574b6 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Промени големина"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сокријте"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликацијата можеби нема да работи со поделен екран"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликацијата не поддржува поделен екран"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
- <string name="divider_title" msgid="5482989479865361192">"Разделник на поделен екран"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Разделник на поделен екран"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Разделник на поделен екран"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левиот на цел екран"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левиот 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левиот 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести долу десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Поставки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отфрли балонче"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Не прикажувај балонче"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Повлечете друга апликација за поделен екран"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Слика од екранот"</string>
<string name="close_text" msgid="4986518933445178928">"Затворете"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Отвори го менито"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index ffa73b09f06e..1ae95e29508f 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"വലുപ്പം മാറ്റുക"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"സ്റ്റാഷ് ചെയ്യൽ"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"സ്‌ക്രീൻ വിഭജന മോഡിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്‌പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
- <string name="divider_title" msgid="5482989479865361192">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ഇടത് പൂർണ്ണ സ്ക്രീൻ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ഇടത് 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ഇടത് 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ക്രമീകരണം"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ബബിൾ ഡിസ്മിസ് ചെയ്യൂ"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ബബിൾ ചെയ്യരുത്"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
<string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"സ്ക്രീൻഷോട്ട്"</string>
<string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"മെനു തുറക്കുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 8b15bf90758c..312a5e450ccc 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Хэмжээг өөрчлөх"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Нуух"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Апп дэлгэцийг хуваах горимтой ажиллахгүй байж магадгүй"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апп дэлгэцийг хуваах горимыг дэмждэггүй"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
- <string name="divider_title" msgid="5482989479865361192">"\"Дэлгэцийг хуваах\" хуваагч"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Дэлгэцийг хуваах хуваагч"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Дэлгэцийг хуваах хуваагч"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Зүүн талын бүтэн дэлгэц"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Зүүн 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Зүүн 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Баруун доош зөөх"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-н тохиргоо"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Бөмбөлгийг хаах"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Бөмбөлөг бүү харуул"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Дэлгэц хуваах горимд ашиглахын тулд өөр аппыг чирнэ үү"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
<string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Дэлгэцийн агшин"</string>
<string name="close_text" msgid="4986518933445178928">"Хаах"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Цэс нээх"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 3e30871a96fe..e2da77f17659 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदला"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"स्टॅश करा"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अ‍ॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करणार नाही"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"अ‍ॅप हे स्प्लिट स्क्रीनला सपोर्ट करत नाही"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अ‍ॅप लाँच होणार नाही."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
- <string name="divider_title" msgid="5482989479865361192">"स्प्लिट-स्क्रीन विभाजक"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन विभाजक"</string>
+ <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन विभाजक"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"डावी फुल स्क्रीन"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"डावी 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"डावी 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"तळाशी उजवीकडे हलवा"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> सेटिंग्ज"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल डिसमिस करा"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल दाखवू नका"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप ड्रॅग करा"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
<string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
<string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करा"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"मेनू उघडा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index 8a8977979217..89654d0a5750 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -24,7 +24,7 @@
<string name="pip_move" msgid="158770205886688553">"हलवा"</string>
<string name="pip_expand" msgid="1051966011679297308">"विस्तार करा"</string>
<string name="pip_collapse" msgid="3903295106641385962">"कोलॅप्स करा"</string>
- <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा दाबा"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा प्रेस करा"</string>
<string name="a11y_pip_menu_entered" msgid="5106343214776801614">"चित्रात-चित्र मेनू."</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"डावीकडे हलवा"</string>
<string name="a11y_action_pip_move_right" msgid="1119409122645529936">"उजवीकडे हलवा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 259930c6a3f6..9965226ed185 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah saiz"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sembunyikan"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Apl mungkin tidak berfungsi dengan skrin pisah"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Apl tidak menyokong skrin pisah"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
- <string name="divider_title" msgid="5482989479865361192">"Pembahagi skrin pisah"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Pembahagi skrin pisah"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Pembahagi skrin pisah"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrin penuh kiri"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Alihkan ke bawah sebelah kanan"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Tetapan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ketepikan gelembung"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Hentikan gelembung"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan jadikan perbualan dalam bentuk gelembung"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bersembang menggunakan gelembung"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Perbualan baharu muncul sebagai ikon terapung atau gelembung. Ketik untuk membuka gelembung. Seret untuk mengalihkan gelembung tersebut."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Seret apl lain untuk skrin pisah"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Seret masuk apl lain untuk menggunakan skrin pisah"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketik dua kali untuk\nalih apl ini"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Tangkapan skrin"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 5e5f79ab6b6c..f44d976aa2d6 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"အရွယ်အစားပြောင်းရန်"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"သိုဝှက်ရန်"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို မပံ့ပိုးပါ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
- <string name="divider_title" msgid="5482989479865361192">"မျက်နှာပြင်ခွဲ၍ပြသသည့် စနစ်"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
+ <string name="divider_title" msgid="1963391955593749442">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ဘယ်ဘက် မျက်နှာပြင်အပြည့်"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ဘယ်ဘက်မျက်နှာပြင် ၇၀%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ဘယ်ဘက် မျက်နှာပြင် ၅၀%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ညာအောက်ခြေသို့ ရွှေ့ပါ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ဆက်တင်များ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ပူဖောင်းကွက် ပယ်ရန်"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ပူဖောင်းကွက် မပြပါနှင့်"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းအတွက် အက်ပ်နောက်တစ်ခုကို ဖိဆွဲပါ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
<string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
<string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"မီနူး ဖွင့်ရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 2c53a7c59299..9cb98733de98 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Endre størrelse"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Oppbevar"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Det kan hende at appen ikke fungerer med delt skjerm"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen støtter ikke delt skjerm"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
- <string name="divider_title" msgid="5482989479865361192">"Skilleelement for delt skjerm"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Skilleelement for delt skjerm"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Skilleelement for delt skjerm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Utvid den venstre delen av skjermen til hele skjermen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sett størrelsen på den venstre delen av skjermen til 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytt til nederst til høyre"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-innstillinger"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ikke vis bobler"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra inn en annen app for å bruke delt skjerm"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra inn en annen app for å bruke delt skjerm"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dobbelttrykk for\nå flytte denne appen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 38dbd15cd3f2..3d4d2bca99c8 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदल्नुहोस्"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"स्ट्यास गर्नुहोस्"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"यो एपले स्प्लिट स्क्रिन मोडमा काम नगर्न सक्छ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यो एप स्प्लिट स्क्रिन मोडमा प्रयोग गर्न मिल्दैन"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
- <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रिन डिभाइडर"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रिन डिभाइडर"</string>
+ <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रिन डिभाइडर"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बायाँ भाग ७०%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बायाँ भाग ५०%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"पुछारमा दायाँतिर सार्नुहोस्"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का सेटिङहरू"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारेज गर्नुहोस्"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल नदेखाइयोस्"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
<string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
<string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"स्क्रिनसट"</string>
<string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"मेनु खोल्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 9cb7ddcf5284..796cb057fcdc 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Formaat aanpassen"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Verbergen"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"De app werkt misschien niet met gesplitst scherm"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App ondersteunt geen gesplitst scherm"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
- <string name="divider_title" msgid="5482989479865361192">"Scheiding voor gesplitst scherm"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Scheiding voor gesplitst scherm"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Scheiding voor gesplitst scherm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Linkerscherm op volledig scherm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Linkerscherm 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Niet als bubbel tonen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatten met bubbels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden als zwevende iconen of bubbels getoond. Tik om een bubbel te openen. Sleep om een bubbel te verplaatsen."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\ndeze app te verplaatsen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Sluiten"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menu openen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index c3bed0a0535c..185ece610464 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ରିସାଇଜ୍ କରନ୍ତୁ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ଲୁଚାନ୍ତୁ"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍‍ ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରେ ଆପ କାମ କରିନପାରେ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
- <string name="divider_title" msgid="5482989479865361192">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
+ <string name="divider_title" msgid="1963391955593749442">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ବାମ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍ କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ବାମ ପଟକୁ 70% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ବାମ ପଟକୁ 50% କରନ୍ତୁ"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ସେଟିଂସ୍"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ବବଲ୍ ଖାରଜ କରନ୍ତୁ"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ବବଲ କରନ୍ତୁ ନାହିଁ"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ସ୍କ୍ରିନସଟ"</string>
<string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 5a1611d830b4..12a9f714b053 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ਆਕਾਰ ਬਦਲੋ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ਸਟੈਸ਼"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
- <string name="divider_title" msgid="5482989479865361192">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ਖੱਬੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ਖੱਬੇ 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ਖੱਬੇ 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ਸੈਟਿੰਗਾਂ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕਰੋ"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ਬਬਲ ਨਾ ਕਰੋ"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
<string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 9f92538a5cb1..ce37cda079cf 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmień rozmiar"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Przenieś do schowka"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacja może nie działać przy podzielonym ekranie"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacja nie obsługuje podzielonego ekranu"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
- <string name="divider_title" msgid="5482989479865361192">"Linia dzielenia ekranu"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Linia dzielenia ekranu"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Linia dzielenia ekranu"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lewa część ekranu na pełnym ekranie"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% lewej części ekranu"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Przenieś w prawy dolny róg"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ustawienia"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zamknij dymek"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nie twórz dymków"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nie wyświetlaj rozmowy jako dymka"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Czatuj, korzystając z dymków"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nowe rozmowy będą wyświetlane jako pływające ikony lub dymki. Kliknij, by otworzyć dymek. Przeciągnij, by go przenieść."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Przeciągnij drugą aplikację, aby podzielić ekran"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Aby podzielić ekran, przeciągnij drugą aplikację"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anuluj"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Aby przenieść aplikację,\nkliknij dwukrotnie"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Zrzut ekranu"</string>
<string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otwórz menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index a46493c829a9..317ebf2b5d5a 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor de tela"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Parar de mostrar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 7413a5b61d9a..708d3d91f58a 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Armazenar"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"A app pode não funcionar com o ecrã dividido"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A app não é compatível com o ecrã dividido"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor do ecrã dividido"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor do ecrã dividido"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor do ecrã dividido"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ecrã esquerdo inteiro"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% no ecrã esquerdo"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Definições de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar balão"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Não apresentar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não apresentar a conversa em balões"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse no chat através de balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"As novas conversas aparecem como ícones flutuantes ou balões. Toque para abrir o balão. Arraste para o mover."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outra app para usar o ecrã dividido"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outra app para usar o ecrã dividido"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes\npara mover esta app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de ecrã"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index a46493c829a9..317ebf2b5d5a 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divisor de tela"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Parar de mostrar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 0d30257600a1..dbd9ac3ee05c 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionează"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stochează"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplicația nu acceptă ecranul împărțit"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
- <string name="divider_title" msgid="5482989479865361192">"Separator pentru ecranul împărțit"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Separator pentru ecranul împărțit"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Separator pentru ecranul împărțit"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Partea stângă pe ecran complet"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Partea stângă: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nu afișa bule"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Atinge de două ori\nca să muți aplicația"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
<string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Captură de ecran"</string>
<string name="close_text" msgid="4986518933445178928">"Închide"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Deschide meniul"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f632f03b19d9..a773b3752d13 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Изменить размер"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Скрыть"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Когда включено разделение экрана, приложение может работать нестабильно."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложение не поддерживает разделение экрана."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
- <string name="divider_title" msgid="5482989479865361192">"Разделитель экрана"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Разделитель экрана"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Разделитель экрана"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левый во весь экран"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левый на 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левый на 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перенести в правый нижний угол"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Скрыть всплывающий чат"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Отключить всплывающие подсказки"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Открыть меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 7825432bbf40..624d7711a625 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ප්‍රතිප්‍රමාණ කරන්න"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"සඟවා තබන්න"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්‍රියා නොකළ හැකිය"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"යෙදුම බෙදීම් තිරය සමග ක්‍රියා නොකළ හැක"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"යෙදුම බෙදුම් තිරයට සහාය නොදක්වයි"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
- <string name="divider_title" msgid="5482989479865361192">"බෙදුම්-තිර වෙන්කරණය"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"බෙදුම් තිර වෙන්කරණය"</string>
+ <string name="divider_title" msgid="1963391955593749442">"බෙදුම් තිර වෙන්කරණය"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"වම් පූර්ණ තිරය"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"වම් 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"වම් 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"පහළ දකුණට ගෙන යන්න"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> සැකසීම්"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"බුබුලු ඉවත ලන්න"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"බුබුළු නොකරන්න"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
<string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
<string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"තිර රුව"</string>
<string name="close_text" msgid="4986518933445178928">"වසන්න"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"මෙනුව විවෘත කරන්න"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 21641e57cfe0..5c7d173df79c 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmeniť veľkosť"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skryť"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikácia nemusí fungovať s rozdelenou obrazovkou"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikácia nepodporuje rozdelenú obrazovku"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
- <string name="divider_title" msgid="5482989479865361192">"Rozdeľovač obrazovky"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Rozdeľovač obrazovky"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Rozdeľovač obrazovky"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ľavá – na celú obrazovku"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ľavá – 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Presunúť doprava nadol"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavenia aplikácie <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavrieť bublinu"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nezobrazovať bubliny"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovať konverzáciu ako bublinu"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Čet pomocou bublín"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nové konverzácie sa zobrazujú ako plávajúce ikony či bubliny. Bublinu otvoríte klepnutím. Premiestnite ju presunutím."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Rozdelenú obrazovku aktivujete presunutím ďalšie aplikácie"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Rozdelenú obrazovku môžete použiť presunutím do inej aplikácie"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Túto aplikáciu\npresuniete dvojitým klepnutím"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Snímka obrazovky"</string>
<string name="close_text" msgid="4986518933445178928">"Zavrieť"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Otvoriť ponuku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 444a3c88b433..03c68ffc435c 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Spremeni velikost"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Zakrij"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
- <string name="divider_title" msgid="5482989479865361192">"Razdelilnik zaslonov"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Razdelilnik zaslonov"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Razdelilnik zaslonov"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levi v celozaslonski način"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premakni spodaj desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavitve za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Opusti oblaček"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne prikazuj oblačkov aplikacij"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Pogovora ne prikaži v oblačku"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klepet z oblački"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi pogovori so prikazani kot lebdeče ikone ali oblački. Če želite odpreti oblaček, se ga dotaknite. Če ga želite premakniti, ga povlecite."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Prekliči"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvakrat se dotaknite\nza premik te aplikacije"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Posnetek zaslona"</string>
<string name="close_text" msgid="4986518933445178928">"Zapri"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Odpri meni"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 07bc2a33ff5e..3878c47a9518 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ndrysho përmasat"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Fshih"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacioni mund të mos funksionojë me ekranin e ndarë"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacioni nuk mbështet ekranin e ndarë"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
- <string name="divider_title" msgid="5482989479865361192">"Ndarësi i ekranit të ndarë"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Ndarësi i ekranit të ndarë"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Ndarësi i ekranit të ndarë"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ekrani i plotë majtas"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Majtas 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Lëvize poshtë djathtas"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cilësimet e <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Hiqe flluskën"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Mos shfaq flluska"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Mos e vendos bisedën në flluskë"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bisedo duke përdorur flluskat"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Bisedat e reja shfaqen si ikona pluskuese ose flluska. Trokit për të hapur flluskën. Zvarrit për ta zhvendosur."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Trokit dy herë për të\nlëvizur këtë aplikacion"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
<string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Pamja e ekranit"</string>
<string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Hap menynë"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 4d75626f99b8..20ccbd771f72 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Промените величину"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ставите у тајну меморију"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликација можда неће радити са подељеним екраном."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликација не подржава подељени екран."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
- <string name="divider_title" msgid="5482989479865361192">"Разделник подељеног екрана"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Разделник подељеног екрана"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Разделник подељеног екрана"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Режим целог екрана за леви екран"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Леви екран 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Леви екран 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести доле десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Подешавања за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Одбаци облачић"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Без облачића"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Превуците другу апликацију да бисте користили подељени екран"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Снимак екрана"</string>
<string name="close_text" msgid="4986518933445178928">"Затворите"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Отворите мени"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 9988e4777e2f..b7035c1c5145 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ändra storlek"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Utför stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Appen kanske inte fungerar med delad skärm"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen har inte stöd för delad skärm"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
- <string name="divider_title" msgid="5482989479865361192">"Avdelare för delad skärm"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Avdelare för delad skärm"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Avdelare för delad skärm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Helskärm på vänster skärm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vänster 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytta längst ned till höger"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Inställningar för <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Stäng bubbla"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Visa inte bubblor"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Visa inte konversationen i bubblor"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatta med bubblor"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nya konversationer visas som flytande ikoner, så kallade bubblor. Tryck på bubblan om du vill öppna den. Dra den om du vill flytta den."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra till en annan app för läget Delad skärm"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra till en annan app för att dela upp skärmen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryck snabbt två gånger\nför att flytta denna app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
<string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skärmbild"</string>
<string name="close_text" msgid="4986518933445178928">"Stäng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Öppna menyn"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 4937464a49fe..cac1debe2f09 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Badilisha ukubwa"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ficha"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Huenda programu isifanye kazi kwenye skrini iliyogawanywa"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programu haifanyi kazi kwenye skrini iliyogawanywa"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
- <string name="divider_title" msgid="5482989479865361192">"Kitenganishi cha kugawa skrini"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Kitenganishi cha kugawa skrini"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Kitenganishi cha kugawa skrini"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrini nzima ya kushoto"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kushoto 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sogeza chini kulia"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mipangilio ya <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ondoa kiputo"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Isifanye viputo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Usiweke viputo kwenye mazungumzo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Piga gumzo ukitumia viputo"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Mazungumzo mapya huonekena kama aikoni au viputo vinavyoelea. Gusa ili ufungue kiputo. Buruta ili ukisogeze."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Buruta ndani programu nyingine ili utumie hali ya skrini iliyogawanywa"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Buruta katika programu nyingine ili utumie skrini iliyogawanywa"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ghairi"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Gusa mara mbili ili\nusogeze programu hii"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Picha ya skrini"</string>
<string name="close_text" msgid="4986518933445178928">"Funga"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Fungua Menyu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index edadfb4916b3..23a2cdc999c9 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"அளவு மாற்று"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"திரைப் பிரிப்புப் பயன்முறையில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"திரைப் பிரிப்புப் பயன்முறையை ஆப்ஸ் ஆதரிக்காது"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
- <string name="divider_title" msgid="5482989479865361192">"திரைப் பிரிப்பான்"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"திரைப் பிரிப்பான்"</string>
+ <string name="divider_title" msgid="1963391955593749442">"திரைப் பிரிப்பான்"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"இடது புறம் முழுத் திரை"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"இடது புறம் 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"இடது புறம் 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"கீழே வலதுபுறமாக நகர்த்து"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> அமைப்புகள்"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"குமிழை அகற்று"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"குமிழ்களைக் காட்ட வேண்டாம்"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
<string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
<string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
@@ -108,4 +110,6 @@
<string name="screenshot_text" msgid="1477704010087786671">"ஸ்கிரீன்ஷாட்"</string>
<string name="close_text" msgid="4986518933445178928">"மூடும்"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
+ <!-- no translation found for expand_menu_text (3847736164494181168) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 66b20518279c..6b415900ab98 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"సైజ్‌ మార్చు"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్‌ పని చేయకపోవచ్చు."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"స్ప్లిట్ స్క్రీన్‌తో యాప్ పని చేయకపోవచ్చు"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"యాప్‌లో స్ప్లిట్ స్క్రీన్‌కు సపోర్ట్ లేదు"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్‌ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
- <string name="divider_title" msgid="5482989479865361192">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
+ <string name="divider_title" msgid="1963391955593749442">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్‌"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"దిగవు కుడివైపునకు జరుపు"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> సెట్టింగ్‌లు"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"బబుల్‌ను విస్మరించు"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"బబుల్‌ను చూపడం ఆపండి"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
<string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
<string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"స్క్రీన్‌షాట్"</string>
<string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"మెనూను తెరవండి"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 67ae9b7c9105..e61904e85b4a 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ปรับขนาด"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"เก็บเข้าที่เก็บส่วนตัว"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"แอปอาจใช้ไม่ได้กับโหมดแยกหน้าจอ"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"แอปไม่รองรับการแยกหน้าจอ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
- <string name="divider_title" msgid="5482989479865361192">"เส้นแยกหน้าจอ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"เส้นแยกหน้าจอ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"เส้นแยกหน้าจอ"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"เต็มหน้าจอทางซ้าย"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ซ้าย 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ซ้าย 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขาวล่าง"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"การตั้งค่า <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ปิดบับเบิล"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ไม่ต้องแสดงบับเบิล"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"ภาพหน้าจอ"</string>
<string name="close_text" msgid="4986518933445178928">"ปิด"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"เปิดเมนู"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 1275dd6095a2..822f7ebcac23 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"I-resize"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"I-stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Posibleng hindi gumana sa split screen ang app"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Hindi sinusuportahan ng app ang split-screen"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
- <string name="divider_title" msgid="5482989479865361192">"Divider ng split-screen"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Divider ng split screen"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Divider ng split screen"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"I-full screen ang nasa kaliwa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Gawing 70% ang nasa kaliwa"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ilipat sa kanan sa ibaba"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mga setting ng <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"I-dismiss ang bubble"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Huwag i-bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Huwag ipakita sa bubble ang mga pag-uusap"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Mag-chat gamit ang bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Lumalabas bilang mga nakalutang na icon o bubble ang mga bagong pag-uusap. I-tap para buksan ang bubble. I-drag para ilipat ito."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Mag-drag ng ibang app para sa split screen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Mag-drag ng isa pang app para sa split screen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"I-double tap para\nilipat ang app na ito"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
<string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
<string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Isara"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Buksan ang Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 74a457f02e22..26081e157086 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Yeniden boyutlandır"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Depola"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Uygulama bölünmüş ekranda çalışmayabilir"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Uygulama bölünmüş ekranı desteklemiyor."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
- <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcı"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcı"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Bölünmüş ekran ayırıcı"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Solda tam ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Solda %70"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sağ alta taşı"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baloncuğu kapat"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Bildirim baloncuğu gösterme"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Görüşmeyi baloncuk olarak görüntüleme"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Baloncukları kullanarak sohbet edin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yeni görüşmeler kayan simgeler veya baloncuk olarak görünür. Açmak için baloncuğa dokunun. Baloncuğu taşımak için sürükleyin."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"İptal"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu uygulamayı taşımak için\niki kez dokunun"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
<string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Ekran görüntüsü"</string>
<string name="close_text" msgid="4986518933445178928">"Kapat"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menüyü Aç"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 0b61d7a85d9e..adbf65648dd1 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -36,7 +36,9 @@
<dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
- <dimen name="pip_menu_elevation">1dp</dimen>
+ <dimen name="pip_menu_elevation_no_menu">1dp</dimen>
+ <dimen name="pip_menu_elevation_move_menu">7dp</dimen>
+ <dimen name="pip_menu_elevation_all_actions_menu">4dp</dimen>
<dimen name="pip_menu_edu_text_view_height">24dp</dimen>
<dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index b1e29f464b89..aacf1ffde371 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Змінити розмір"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сховати"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Додаток може не працювати в режимі розділення екрана"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Додаток не підтримує розділення екрана"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
- <string name="divider_title" msgid="5482989479865361192">"Розділювач екрана"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Розділювач екрана"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Розділювач екрана"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ліве вікно на весь екран"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ліве вікно на 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ліве вікно на 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перемістити праворуч униз"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налаштування параметра \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Закрити підказку"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Не показувати спливаючі чати"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Знімок екрана"</string>
<string name="close_text" msgid="4986518933445178928">"Закрити"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Відкрити меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 88240cbc08d7..f494e08166a9 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -21,8 +21,8 @@
<string name="pip_phone_expand" msgid="2579292903468287504">"پھیلائیں"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string>
- <string name="pip_menu_title" msgid="5393619322111827096">"مینو"</string>
- <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینو"</string>
+ <string name="pip_menu_title" msgid="5393619322111827096">"مینیو"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینیو"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> تصویر میں تصویر میں ہے"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"اگر آپ نہیں چاہتے ہیں کہ <xliff:g id="NAME">%s</xliff:g> اس خصوصیت کا استعمال کرے تو ترتیبات کھولنے کے لیے تھپتھپا کر اسے آف کرے۔"</string>
<string name="pip_play" msgid="3496151081459417097">"چلائیں"</string>
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"سائز تبدیل کریں"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ایپ اسپلٹ اسکرین کو سپورٹ نہیں کرتی ہے"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
- <string name="divider_title" msgid="5482989479865361192">"اسپلٹ اسکرین ڈیوائیڈر"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"اسپلٹ اسکرین ڈیوائیڈر"</string>
+ <string name="divider_title" msgid="1963391955593749442">"اسپلٹ اسکرین ڈیوائیڈر"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"بائیں فل اسکرین"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"بائیں %70"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"بائیں %50"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نیچے دائیں جانب لے جائیں"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ترتیبات"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"بلبلہ برخاست کریں"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"بلبلہ دکھانا بند کریں"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
<string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
<string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
@@ -107,5 +109,6 @@
<string name="select_text" msgid="5139083974039906583">"منتخب کریں"</string>
<string name="screenshot_text" msgid="1477704010087786671">"اسکرین شاٹ"</string>
<string name="close_text" msgid="4986518933445178928">"بند کریں"</string>
- <string name="collapse_menu_text" msgid="7515008122450342029">"مینو بند کریں"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"مینو کھولیں"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index 42b9564ff549..d0f011c0b4cf 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -25,7 +25,7 @@
<string name="pip_expand" msgid="1051966011679297308">"پھیلائیں"</string>
<string name="pip_collapse" msgid="3903295106641385962">"سکیڑیں"</string>
<string name="pip_edu_text" msgid="7930546669915337998">"کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" کو دو بار دبائیں"</string>
- <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینو۔"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینیو۔"</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"دائیں منتقل کریں"</string>
<string name="a11y_action_pip_move_right" msgid="1119409122645529936">"بائیں منتقل کریں"</string>
<string name="a11y_action_pip_move_up" msgid="98502616918621959">"اوپر منتقل کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 38f3a0dd1091..9b5aa6fcfade 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Oʻlchamini oʻzgartirish"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Berkitish"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Bu ilovada ekranni ikkiga ajratish rejimi ishlamaydi."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Bu ilovada ekranni ikkiga ajratish ishlamaydi."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
- <string name="divider_title" msgid="5482989479865361192">"Ekranni ikkiga ajratish chizigʻi"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Ekranni ikkiga ajratish chizigʻi"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Ekranni ikkiga ajratish chizigʻi"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Chapda to‘liq ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Chapda 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Quyi oʻngga surish"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> sozlamalari"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bulutchani yopish"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Qalqib chiqmasin"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Suhbatlar bulutchalar shaklida chiqmasin"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bulutchalar yordamida subhatlashish"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yangi xabarlar qalqib chiquvchi belgilar yoki bulutchalar kabi chiqadi. Xabarni ochish uchun bildirishnoma ustiga bosing. Xabarni qayta joylash uchun bildirishnomani suring."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Bekor qilish"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu ilovani siljitish uchun\nikki marta bosing"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
<string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Skrinshot"</string>
<string name="close_text" msgid="4986518933445178928">"Yopish"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menyuni ochish"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 576bd5b12465..fe5461ecd1d7 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Đổi kích thước"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ẩn"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Có thể ứng dụng không dùng được chế độ chia đôi màn hình"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Ứng dụng không hỗ trợ chế độ chia đôi màn hình"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
- <string name="divider_title" msgid="5482989479865361192">"Bộ chia màn hình"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Trình chia đôi màn hình"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Trình chia đôi màn hình"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Toàn màn hình bên trái"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Trái 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Chuyển tới dưới cùng bên phải"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cài đặt <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Đóng bong bóng"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Không hiện bong bóng trò chuyện"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Dừng sử dụng bong bóng cho cuộc trò chuyện"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Trò chuyện bằng bong bóng trò chuyện"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Các cuộc trò chuyện mới sẽ xuất hiện dưới dạng biểu tượng nổi hoặc bong bóng trò chuyện. Nhấn để mở bong bóng trò chuyện. Kéo để di chuyển bong bóng trò chuyện."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Kéo vào một ứng dụng khác để chia đôi màn hình"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Kéo một ứng dụng khác vào để chia đôi màn hình"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Huỷ"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Nhấn đúp để\ndi chuyển ứng dụng này"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
<string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Ảnh chụp màn hình"</string>
<string name="close_text" msgid="4986518933445178928">"Đóng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Mở Trình đơn"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index a6c6ac449bf2..040aff836a5a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"调整大小"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"隐藏"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"应用可能无法在分屏模式下正常运行"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"应用不支持分屏"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
- <string name="divider_title" msgid="5482989479865361192">"分屏分隔线"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"分屏分隔线"</string>
+ <string name="divider_title" msgid="1963391955593749442">"分屏分隔线"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左侧全屏"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左侧 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左侧 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下角"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"不显示对话泡"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一个应用,即可使用分屏模式"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"屏幕截图"</string>
<string name="close_text" msgid="4986518933445178928">"关闭"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"打开菜单"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index d6df8c1e342c..5fac19b4f316 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"調整大小"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"保護"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割螢幕中運作"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"應用程式不支援分割螢幕"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
- <string name="divider_title" msgid="5482989479865361192">"分割螢幕分隔線"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"分割螢幕分隔線"</string>
+ <string name="divider_title" msgid="1963391955593749442">"分割螢幕分隔線"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左邊全螢幕"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左邊 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左邊 50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移去右下角"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉小視窗氣泡"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"不要顯示對話氣泡"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一個應用程式即可分割螢幕"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"打開選單"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 64980c036d0b..0a2533539867 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"調整大小"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"暫時隱藏"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割畫面中運作"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"這個應用程式不支援分割畫面"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
- <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
- <string name="divider_title" msgid="5482989479865361192">"分割畫面分隔線"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"分割畫面分隔線"</string>
+ <string name="divider_title" msgid="1963391955593749442">"分割畫面分隔線"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"以全螢幕顯示左側畫面"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"以 70% 的螢幕空間顯示左側畫面"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"以 50% 的螢幕空間顯示左側畫面"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下方"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉對話框"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"不要顯示對話框"</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>
@@ -84,7 +85,7 @@
<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="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖進另一個應用程式即可使用分割畫面模式"</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>
@@ -93,6 +94,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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"開啟選單"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index abbec335312c..5b85be2d262c 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -32,13 +32,13 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Shintsha usayizi"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Yenza isiteshi"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
- <string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Ama-app okungenzeka angasebenzi ngesikrini esihlukanisiwe"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"I-app ayisekeli isikrini esihlukanisiwe."</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
- <string name="divider_title" msgid="5482989479865361192">"Isihlukanisi sokuhlukanisa isikrini"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"Isihlukanisi sokuhlukanisa isikrini"</string>
+ <string name="divider_title" msgid="1963391955593749442">"Isihlukanisi sokuhlukanisa isikrini"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Isikrini esigcwele esingakwesokunxele"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kwesokunxele ngo-70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string>
@@ -68,6 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Hambisa inkinobho ngakwesokudla"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> izilungiselelo"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cashisa ibhamuza"</string>
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ungabhamuzi"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ungayibhamuzi ingxoxo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xoxa usebenzisa amabhamuza"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Izingxoxo ezintsha zivela njengezithonjana ezintantayo, noma amabhamuza. Thepha ukuze uvule ibhamuza. Hudula ukuze ulihambise."</string>
@@ -84,7 +85,7 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string>
@@ -93,6 +94,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Thepha kabili ukuze\nuhambise le-app"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
<string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
@@ -108,4 +110,5 @@
<string name="screenshot_text" msgid="1477704010087786671">"Isithombe-skrini"</string>
<string name="close_text" msgid="4986518933445178928">"Vala"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
+ <string name="expand_menu_text" msgid="3847736164494181168">"Vula Imenyu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index 2aad4c1c1805..fbb5caa508de 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6fb70006e67f..c487e4afd678 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -54,4 +54,17 @@
<color name="splash_screen_bg_light">#FFFFFF</color>
<color name="splash_screen_bg_dark">#000000</color>
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
+
+ <!-- Desktop Mode -->
+ <color name="desktop_mode_caption_handle_bar_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_handle_bar_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_expand_button_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_expand_button_dark">#48473A</color>
+ <color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
+ <color name="desktop_mode_caption_menu_buttons_color_inactive">#191C1D</color>
+ <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 680ad5101366..2be34c90a661 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -226,6 +226,10 @@
<dimen name="bubble_user_education_padding_end">58dp</dimen>
<!-- Padding between the bubble and the user education text. -->
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
+ <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
+ <dimen name="bubblebar_size">72dp</dimen>
+ <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
+ <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
@@ -327,6 +331,12 @@
<!-- The vertical padding for the buttons in the letterbox restart dialog -->
<dimen name="letterbox_restart_dialog_vertical_padding">8dp</dimen>
+ <!-- The margin between the reachability dialog container and its parent. -->
+ <dimen name="letterbox_reachability_education_dialog_margin">16dp</dimen>
+
+ <!-- The size of the icon in the item of reachability education -->
+ <dimen name="letterbox_reachability_education_item_image_size">24dp</dimen>
+
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
@@ -361,19 +371,34 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
- <!-- Width of buttons (32dp each) + padding (128dp total). -->
- <dimen name="freeform_decor_caption_menu_width">256dp</dimen>
+ <!-- The width of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
- <dimen name="freeform_decor_caption_menu_height">250dp</dimen>
+ <!-- The height of the handle menu's "App Info" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
- <dimen name="freeform_resize_handle">30dp</dimen>
+ <!-- The height of the handle menu's "Windowing" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
- <dimen name="freeform_resize_corner">44dp</dimen>
+ <!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
- <!-- The radius of the caption menu shadow. -->
- <dimen name="caption_menu_shadow_radius">4dp</dimen>
+ <!-- The top margin of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
+
+ <!-- The start margin of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
+
+ <!-- The margin between pills of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
<!-- The radius of the caption menu corners. -->
- <dimen name="caption_menu_corner_radius">20dp</dimen>
+ <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
+
+ <!-- The radius of the caption menu shadow. -->
+ <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
+ <dimen name="freeform_resize_handle">30dp</dimen>
+
+ <dimen name="freeform_resize_corner">44dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 3082962e1a8b..b192fdf245e2 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -67,10 +67,10 @@
<string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
<!-- Multi-Window strings -->
- <!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
- <string name="dock_forced_resizable">App may not work with split-screen.</string>
+ <!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
+ <string name="dock_forced_resizable">App may not work with split screen</string>
<!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead [CHAR LIMIT=NONE] -->
- <string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
+ <string name="dock_non_resizeble_failed_to_dock_text">App does not support split screen</string>
<!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
<string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
@@ -78,10 +78,10 @@
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
<string name="activity_launch_on_secondary_display_failed_text">App does not support launch on secondary displays.</string>
- <!-- Accessibility label and window tile for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] -->
- <string name="accessibility_divider">Split-screen divider</string>
- <!-- Accessibility window title for the split-screen divider window [CHAR LIMIT=NONE] -->
- <string name="divider_title">Split-screen divider</string>
+ <!-- Accessibility label and window tile for the divider that separates the windows in split screen mode [CHAR LIMIT=NONE] -->
+ <string name="accessibility_divider">Split screen divider</string>
+ <!-- Accessibility window title for the split screen divider window [CHAR LIMIT=NONE] -->
+ <string name="divider_title">Split screen divider</string>
<!-- Accessibility action for moving docked stack divider to make the left screen full screen [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_left_full">Left full screen</string>
@@ -191,7 +191,7 @@
<string name="letterbox_education_dialog_title">See and do more</string>
<!-- Description of the split screen action. [CHAR LIMIT=NONE] -->
- <string name="letterbox_education_split_screen_text">Drag in another app for split-screen</string>
+ <string name="letterbox_education_split_screen_text">Drag in another app for split screen</string>
<!-- Description of the reposition app action. [CHAR LIMIT=NONE] -->
<string name="letterbox_education_reposition_text">Double-tap outside an app to reposition it</string>
@@ -219,6 +219,18 @@
<!-- Checkbox text for asking to not show the restart confirmation dialog again. [CHAR LIMIT=NONE] -->
<string name="letterbox_restart_dialog_checkbox_title">Don\u2019t show again</string>
+ <!-- When an app is letterboxed, it is initially centered on the screen but the user can
+ double tap to move the app to a different position. With a double-tap on the right,
+ the app moves the right of the screen and with a double-tap on the left the app moves
+ on the left. The same happens if the app has space to be moved to the top or bottom of
+ the screen. This time the double-tap can happen on the top or bottom of the screen.
+ To teach the user about this feature, we display an education explaining how the double-tap
+ works and how the app can be moved on the screen.
+ This is the text we show to the user below an icon visualizing the double-tap
+ action. The description should be split in two lines separated by "\n" like for this
+ locale. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_reachability_reposition_text">Double-tap to\nmove this app</string>
+
<!-- Freeform window caption strings -->
<!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
<string name="maximize_button_text">Maximize</string>
@@ -250,4 +262,6 @@
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
+ <!-- Accessibility text for the handle menu open menu button [CHAR LIMIT=NONE] -->
+ <string name="expand_menu_text">Open Menu</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index bc2e71d1c013..ee80c4723b8d 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,25 +30,31 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
- <style name="CaptionButtonStyle">
- <item name="android:layout_width">32dp</item>
- <item name="android:layout_height">32dp</item>
- <item name="android:layout_margin">5dp</item>
- <item name="android:padding">4dp</item>
+ <style name="DesktopModeHandleMenuActionButton">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">52dp</item>
+ <item name="android:gravity">start|center_vertical</item>
+ <item name="android:padding">16dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textFontWeight">500</item>
+ <item name="android:textColor">@color/desktop_mode_caption_menu_text_color</item>
+ <item name="android:drawablePadding">16dp</item>
+ <item name="android:background">?android:selectableItemBackground</item>
</style>
- <style name="CaptionWindowingButtonStyle">
- <item name="android:layout_width">40dp</item>
- <item name="android:layout_height">40dp</item>
- <item name="android:padding">4dp</item>
+ <style name="DesktopModeHandleMenuWindowingButton">
+ <item name="android:layout_width">48dp</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:padding">14dp</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:background">?android:selectableItemBackgroundBorderless</item>
</style>
- <style name="CaptionMenuButtonStyle" parent="@style/Widget.AppCompat.Button.Borderless">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">52dp</item>
- <item name="android:layout_marginStart">10dp</item>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
<item name="android:padding">4dp</item>
- <item name="android:gravity">start|center_vertical</item>
</style>
<style name="DockedDividerBackground">
@@ -144,4 +150,20 @@
@*android:string/config_bodyFontFamily
</item>
</style>
+
+ <style name="ReachabilityEduHandLayout">
+ <item name="android:focusable">false</item>
+ <item name="android:focusableInTouchMode">false</item>
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:contentDescription">@string/restart_button_description</item>
+ <item name="android:visibility">invisible</item>
+ <item name="android:lineSpacingExtra">-1sp</item>
+ <item name="android:textSize">12sp</item>
+ <item name="android:textAlignment">center</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textAppearance">
+ @*android:style/TextAppearance.DeviceDefault.Body2
+ </item>
+ </style>
+
</resources>
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 585f81c81a36..b6fd0bbafc71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -428,9 +428,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@Override
- public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
- mStartingWindow.addStartingWindow(info, appToken);
+ mStartingWindow.addStartingWindow(info);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 521a65cc4df6..bfbddbbe4aa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static java.util.Objects.requireNonNull;
import android.content.Context;
+import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -35,6 +36,9 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.List;
/**
* Responsible for handling ActivityEmbedding related transitions.
@@ -86,12 +90,13 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean containsEmbeddingSplit = false;
- for (TransitionInfo.Change change : info.getChanges()) {
+ boolean containsNonEmbeddedChange = false;
+ final List<TransitionInfo.Change> changes = info.getChanges();
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- // Only animate the transition if all changes are in a Task with ActivityEmbedding.
- return false;
- }
- if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) {
+ containsNonEmbeddedChange = true;
+ } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
containsEmbeddingSplit = true;
@@ -103,6 +108,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
// such as the device is in a folded state.
return false;
}
+ if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+ return false;
+ }
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
@@ -110,6 +118,37 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return true;
}
+ private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
+ final Rect nonClosingEmbeddedArea = new Rect();
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!TransitionUtil.isClosingType(change.getMode())) {
+ if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ nonClosingEmbeddedArea.union(change.getEndAbsBounds());
+ continue;
+ }
+ // Not able to handle non-embedded container if it is not closing.
+ return false;
+ }
+ }
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
+ && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) {
+ // Unknown to animate containers outside the area of embedded activities.
+ return false;
+ }
+ }
+ // Drop the non-embedded closing change because it is occluded by embedded activities.
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ changes.remove(i);
+ }
+ }
+ return true;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8cbe44b15e42..133fd87a2f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -33,12 +33,19 @@ public interface BackAnimation {
*
* @param touchX the X touch position of the {@link MotionEvent}.
* @param touchY the Y touch position of the {@link MotionEvent}.
+ * @param velocityX the X velocity computed from the {@link MotionEvent}.
+ * @param velocityY the Y velocity computed from the {@link MotionEvent}.
* @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
* the process. This is forwarded separately because the input pipeline may mutate
* the {#event} action state later.
* @param swipeEdge the edge from which the swipe begins.
*/
- void onBackMotion(float touchX, float touchY, int keyAction,
+ void onBackMotion(
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
@BackEvent.SwipeEdge int swipeEdge);
/**
@@ -52,4 +59,10 @@ public interface BackAnimation {
* @param progressThreshold the max threshold to keep progressing back animation.
*/
void setSwipeThresholds(float triggerThreshold, float progressThreshold);
+
+ /**
+ * Sets the system bar listener to control the system bar color.
+ * @param customizer the controller to control system bar color.
+ */
+ void setStatusBarCustomizer(StatusBarCustomizer customizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 36cf29a4c4f3..9bf3b80d262e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -17,11 +17,17 @@
package com.android.wm.shell.back;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.annotation.NonNull;
import android.graphics.Color;
+import android.graphics.Rect;
import android.view.SurfaceControl;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.view.AppearanceRegion;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
/**
@@ -29,18 +35,35 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
*/
public class BackAnimationBackground {
private static final int BACKGROUND_LAYER = -1;
+
+ private static final int NO_APPEARANCE = 0;
+
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private SurfaceControl mBackgroundSurface;
+ private StatusBarCustomizer mCustomizer;
+ private boolean mIsRequestingStatusBarAppearance;
+ private boolean mBackgroundIsDark;
+ private Rect mStartBounds;
+
public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
}
- void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+ /**
+ * Ensures the back animation background color layer is present.
+ * @param startRect The start bounds of the closing target.
+ * @param color The background color.
+ * @param transaction The animation transaction.
+ */
+ void ensureBackground(Rect startRect, int color,
+ @NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface != null) {
return;
}
+ mBackgroundIsDark = ColorUtils.calculateLuminance(color) < 0.5f;
+
final float[] colorComponents = new float[] { Color.red(color) / 255.f,
Color.green(color) / 255.f, Color.blue(color) / 255.f };
@@ -54,6 +77,8 @@ public class BackAnimationBackground {
transaction.setColor(mBackgroundSurface, colorComponents)
.setLayer(mBackgroundSurface, BACKGROUND_LAYER)
.show(mBackgroundSurface);
+ mStartBounds = startRect;
+ mIsRequestingStatusBarAppearance = false;
}
void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
@@ -65,5 +90,31 @@ public class BackAnimationBackground {
transaction.remove(mBackgroundSurface);
}
mBackgroundSurface = null;
+ mIsRequestingStatusBarAppearance = false;
+ }
+
+ void setStatusBarCustomizer(StatusBarCustomizer customizer) {
+ mCustomizer = customizer;
+ }
+
+ void onBackProgressed(float progress) {
+ if (mCustomizer == null || mStartBounds.isEmpty()) {
+ return;
+ }
+
+ final boolean shouldCustomizeSystemBar = progress > UPDATE_SYSUI_FLAGS_THRESHOLD;
+ if (shouldCustomizeSystemBar == mIsRequestingStatusBarAppearance) {
+ return;
+ }
+
+ mIsRequestingStatusBarAppearance = shouldCustomizeSystemBar;
+ if (mIsRequestingStatusBarAppearance) {
+ final AppearanceRegion region = new AppearanceRegion(!mBackgroundIsDark
+ ? APPEARANCE_LIGHT_STATUS_BARS : NO_APPEARANCE,
+ mStartBounds);
+ mCustomizer.customizeStatusBarAppearance(region);
+ } else {
+ mCustomizer.customizeStatusBarAppearance(null);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
new file mode 100644
index 000000000000..e06d3ef4e1ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -0,0 +1,25 @@
+/*
+ * 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.back;
+
+/**
+ * The common constant values used in back animators.
+ */
+class BackAnimationConstants {
+ static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
+ static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0b8759890359..dc27ceb7f51c 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
@@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
@@ -37,7 +40,9 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.MathUtils;
import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
@@ -55,6 +60,8 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -79,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
public static boolean IS_U_ANIMATION_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
SETTING_VALUE_ON) == SETTING_VALUE_ON;
+
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+
+ /**
+ * The maximum additional progress in case of fling gesture.
+ * The end animation starts after the user lifts the finger from the screen, we continue to
+ * fire {@link BackEvent}s until the velocity reaches 0.
+ */
+ private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */
+
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
/**
@@ -95,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
private boolean mTriggerBack;
+ private FlingAnimationUtils mFlingAnimationUtils;
@Nullable
private BackNavigationInfo mBackNavigationInfo;
@@ -120,6 +139,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable
private IOnBackInvokedCallback mActiveCallback;
+ private CrossActivityAnimation mDefaultActivityAnimation;
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
new RemoteCallback.OnResultListener() {
@@ -139,6 +161,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
});
private final BackAnimationBackground mAnimationBackground;
+ private StatusBarCustomizer mCustomizer;
public BackAnimationController(
@NonNull ShellInit shellInit,
@@ -169,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mAnimationBackground = backAnimationBackground;
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics)
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
}
@VisibleForTesting
@@ -194,10 +222,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
new CrossTaskBackAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
crossTaskAnimation.mBackAnimationRunner);
- final CrossActivityAnimation crossActivityAnimation =
+ mDefaultActivityAnimation =
new CrossActivityAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- crossActivityAnimation.mBackAnimationRunner);
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ mCustomizeActivityAnimation =
+ new CustomizeActivityAnimation(mContext, mAnimationBackground);
// TODO (236760237): register dialog close animation when it's completed.
}
@@ -249,8 +279,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private class BackAnimationImpl implements BackAnimation {
@Override
public void onBackMotion(
- float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
- mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
+ @BackEvent.SwipeEdge int swipeEdge
+ ) {
+ mShellExecutor.execute(() -> onMotionEvent(
+ /* touchX = */ touchX,
+ /* touchY = */ touchY,
+ /* velocityX = */ velocityX,
+ /* velocityY = */ velocityY,
+ /* keyAction = */ keyAction,
+ /* swipeEdge = */ swipeEdge));
}
@Override
@@ -263,6 +305,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
triggerThreshold, progressThreshold));
}
+
+ @Override
+ public void setStatusBarCustomizer(StatusBarCustomizer customizer) {
+ mCustomizer = customizer;
+ mAnimationBackground.setStatusBarCustomizer(customizer);
+ }
}
private static class IBackAnimationImpl extends IBackAnimation.Stub
@@ -289,12 +337,23 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
BackNavigationInfo.TYPE_RETURN_TO_HOME));
}
+ public void customizeStatusBarAppearance(AppearanceRegion appearance) {
+ executeRemoteCallWithTaskPermission(mController, "useLauncherSysBarFlags",
+ (controller) -> controller.customizeStatusBarAppearance(appearance));
+ }
+
@Override
public void invalidate() {
mController = null;
}
}
+ private void customizeStatusBarAppearance(AppearanceRegion appearance) {
+ if (mCustomizer != null) {
+ mCustomizer.customizeStatusBarAppearance(appearance);
+ }
+ }
+
void registerAnimation(@BackNavigationInfo.BackTargetType int type,
@NonNull BackAnimationRunner runner) {
mAnimationDefinition.set(type, runner);
@@ -308,13 +367,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
- public void onMotionEvent(float touchX, float touchY, int keyAction,
+ public void onMotionEvent(
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
if (mPostCommitAnimationInProgress) {
return;
}
- mTouchTracker.update(touchX, touchY);
+ mTouchTracker.update(touchX, touchY, velocityX, velocityY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -368,7 +432,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
if (mAnimationDefinition.contains(backType)) {
- mActiveCallback = mAnimationDefinition.get(backType).getCallback();
mAnimationDefinition.get(backType).startGesture();
} else {
mActiveCallback = null;
@@ -380,8 +443,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onMove() {
- if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
- || mActiveCallback == null) {
+ if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) {
return;
}
@@ -420,14 +482,84 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackStarted(backEvent);
- }
+ callback.onBackStarted(backEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
}
+
+ /**
+ * Allows us to manage the fling gesture, it smoothly animates the current progress value to
+ * the final position, calculated based on the current velocity.
+ *
+ * @param callback the callback to be invoked when the animation ends.
+ */
+ private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) {
+ if (callback == null) {
+ return;
+ }
+
+ boolean animationStarted = false;
+
+ if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) {
+
+ final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent();
+ if (backMotionEvent != null) {
+ // Constraints - absolute values
+ float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
+ float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
+ float maxX = mTouchTracker.getMaxX(); // px
+ float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
+
+ // Current state
+ float currentX = backMotionEvent.getTouchX();
+ float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(),
+ -maxVelocity, maxVelocity);
+
+ // Target state
+ float animationFaction = velocity / maxVelocity; // value between -1 and 1
+ float flingDistance = animationFaction * maxFlingDistance; // px
+ float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX);
+
+ if (!Float.isNaN(endX)
+ && currentX != endX
+ && Math.abs(velocity) >= minVelocity) {
+ ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX);
+
+ mFlingAnimationUtils.apply(
+ /* animator = */ animator,
+ /* currValue = */ currentX,
+ /* endValue = */ endX,
+ /* velocity = */ velocity,
+ /* maxDistance = */ maxFlingDistance
+ );
+
+ animator.addUpdateListener(animation -> {
+ Float animatedValue = (Float) animation.getAnimatedValue();
+ float progress = mTouchTracker.getProgress(animatedValue);
+ final BackMotionEvent backEvent = mTouchTracker
+ .createProgressEvent(progress);
+ dispatchOnBackProgressed(mActiveCallback, backEvent);
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnBackInvoked(callback);
+ }
+ });
+ animator.start();
+ animationStarted = true;
+ }
+ }
+ }
+
+ if (!animationStarted) {
+ dispatchOnBackInvoked(callback);
+ }
+ }
+
private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
@@ -444,9 +576,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackCancelled();
- }
+ callback.onBackCancelled();
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
@@ -458,19 +588,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackProgressed(backEvent);
- }
+ callback.onBackProgressed(backEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
- private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
- // TODO(b/258698745): Only dispatch to animation callbacks.
- return mEnableAnimations.get();
- }
-
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -502,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mBackNavigationInfo != null) {
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
if (mTriggerBack) {
- dispatchOnBackInvoked(callback);
+ dispatchOrAnimateOnBackInvoked(callback);
} else {
dispatchOnBackCancelled(callback);
}
@@ -542,15 +665,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
final int backType = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = mAnimationDefinition.get(backType);
// Simply trigger and finish back navigation when no animator defined.
- if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+ if (!shouldDispatchToAnimator() || runner == null) {
invokeOrCancelBack();
return;
}
-
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
if (runner.isWaitingAnimation()) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+ // Supposed it is in post commit animation state, and start the timeout to watch
+ // if the animation is ready.
+ mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
return;
} else if (runner.isAnimationCancelled()) {
invokeOrCancelBack();
@@ -567,13 +692,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mPostCommitAnimationInProgress) {
return;
}
+
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
mPostCommitAnimationInProgress = true;
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
// The next callback should be {@link #onBackAnimationFinished}.
if (mTriggerBack) {
- dispatchOnBackInvoked(mActiveCallback);
+ dispatchOrAnimateOnBackInvoked(mActiveCallback);
} else {
dispatchOnBackCancelled(mActiveCallback);
}
@@ -585,9 +712,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@VisibleForTesting
void onBackAnimationFinished() {
- if (!mPostCommitAnimationInProgress) {
- return;
- }
// Stop timeout runner.
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
mPostCommitAnimationInProgress = false;
@@ -607,6 +731,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
mActiveCallback = null;
+ // reset to default
+ if (mDefaultActivityAnimation != null
+ && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ }
if (mBackNavigationInfo != null) {
mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
mBackNavigationInfo = null;
@@ -614,14 +744,35 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTriggerBack = false;
}
+ private BackAnimationRunner getAnimationRunnerAndInit() {
+ int type = mBackNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ final BackNavigationInfo.CustomAnimationInfo animationInfo =
+ mBackNavigationInfo.getCustomAnimationInfo();
+ if (animationInfo != null && mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.mBackAnimationRunner);
+ }
+ }
+ return mAnimationDefinition.get(type);
+ }
+
private void createAdapter() {
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
@Override
- public void onAnimationStart(int type, RemoteAnimationTarget[] apps,
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(() -> {
- final BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (mBackNavigationInfo == null) {
+ Log.e(TAG, "Lack of navigation info to start animation.");
+ return;
+ }
+ final int type = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = getAnimationRunnerAndInit();
if (runner == null) {
Log.e(TAG, "Animation didn't be defined for type "
+ BackNavigationInfo.typeToString(type));
@@ -634,6 +785,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
return;
}
+ mActiveCallback = runner.getCallback();
mBackAnimationFinishedCallback = finishedCallback;
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
@@ -645,11 +797,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
}
+ // Dispatch the first progress after animation start for smoothing the initial
+ // animation, instead of waiting for next onMove.
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
startPostCommitAnimation();
}
});
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 82c523f337db..913239f74bf2 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
@@ -75,7 +75,7 @@ class BackAnimationRunner {
};
mWaitingAnimation = false;
try {
- mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
+ getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onAnimationStart", e);
@@ -99,4 +99,8 @@ class BackAnimationRunner {
boolean isAnimationCancelled() {
return mAnimationCancelled;
}
+
+ void resetWaitingAnimation() {
+ mWaitingAnimation = false;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index da113cb579d6..22c90153bb39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -89,7 +90,6 @@ class CrossActivityAnimation {
private static final float WINDOW_X_SHIFT_DP = 96;
private static final int SCALE_FACTOR = 100;
// TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
- private static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
@@ -184,7 +184,7 @@ class CrossActivityAnimation {
mStartTaskRect.offsetTo(0, 0);
// Draw background with task background color.
- mBackground.ensureBackground(
+ mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
}
@@ -244,6 +244,7 @@ class CrossActivityAnimation {
: mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
mLeavingProgressSpring.animateToFinalPosition(springProgress);
mEnteringProgressSpring.animateToFinalPosition(springProgress);
+ mBackground.onBackProgressed(progress);
}
private void onGestureCommitted() {
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 99a434aff799..a7dd27a0784f 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
@@ -141,7 +141,8 @@ class CrossTaskBackAnimation {
mStartTaskRect.offsetTo(0, 0);
// Draw background.
- mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
+ mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
+ BACKGROUNDCOLOR, mTransaction);
}
private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -189,6 +190,8 @@ class CrossTaskBackAnimation {
applyColorTransform(mClosingTarget.leash, closingColorScale);
applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
mTransaction.apply();
+
+ mBackground.onBackProgressed(progress);
}
private void updatePostCommitClosingAnimation(float progress) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
new file mode 100644
index 000000000000..f0c5d8b29b2f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -0,0 +1,419 @@
+/*
+ * 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.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.view.Choreographer;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackNavigationInfo;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.R;
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Class that handle customized close activity transition animation.
+ */
+@ShellMainThread
+class CustomizeActivityAnimation {
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+ private final float mCornerRadius;
+ private final SurfaceControl.Transaction mTransaction;
+ private final BackAnimationBackground mBackground;
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 250;
+
+ private static final int SCALE_FACTOR = 1000;
+ private final SpringAnimation mProgressSpring;
+ private float mLatestProgress = 0.0f;
+
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+ final CustomAnimationLoader mCustomAnimationLoader;
+ private Animation mEnterAnimation;
+ private Animation mCloseAnimation;
+ private int mNextBackgroundColor;
+ final Transformation mTransformation = new Transformation();
+
+ private final Choreographer mChoreographer;
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ this(context, background, new SurfaceControl.Transaction(), null);
+ }
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background,
+ SurfaceControl.Transaction transaction, Choreographer choreographer) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackground = background;
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mCustomAnimationLoader = new CustomAnimationLoader(context);
+
+ mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
+ mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+ }
+
+ private float getLatestProgress() {
+ return mLatestProgress * SCALE_FACTOR;
+ }
+ private void setLatestProgress(float value) {
+ mLatestProgress = value / SCALE_FACTOR;
+ applyTransformTransaction(mLatestProgress);
+ }
+
+ private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter") {
+ @Override
+ public void setValue(CustomizeActivityAnimation anim, float value) {
+ anim.setLatestProgress(value);
+ }
+
+ @Override
+ public Float get(CustomizeActivityAnimation object) {
+ return object.getLatestProgress();
+ }
+ };
+
+ // The target will lose focus when alpha == 0, so keep a minimum value for it.
+ private static float keepMinimumAlpha(float transAlpha) {
+ return Math.max(transAlpha, 0.005f);
+ }
+
+ private static void initializeAnimation(Animation animation, Rect bounds) {
+ final int width = bounds.width();
+ final int height = bounds.height();
+ animation.initialize(width, height, width, height);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
+ initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
+
+ // Draw background with task background color.
+ if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
+ mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
+ mNextBackgroundColor == Color.TRANSPARENT
+ ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
+ : mNextBackgroundColor,
+ mTransaction);
+ }
+ }
+
+ private void applyTransformTransaction(float progress) {
+ if (mClosingTarget == null || mEnteringTarget == null) {
+ return;
+ }
+ applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
+ applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ }
+
+ private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
+ mTransformation.clear();
+ animation.getTransformationAt(progress, mTransformation);
+ mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
+ mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
+ mTransaction.setCornerRadius(leash, mCornerRadius);
+ }
+
+ void finishAnimation() {
+ if (mCloseAnimation != null) {
+ mCloseAnimation.reset();
+ mCloseAnimation = null;
+ }
+ if (mEnterAnimation != null) {
+ mEnterAnimation.reset();
+ mEnterAnimation = null;
+ }
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ mTransformation.clear();
+ mLatestProgress = 0;
+ mNextBackgroundColor = Color.TRANSPARENT;
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ mProgressSpring.animateToFinalPosition(0);
+ mProgressSpring.skipToEnd();
+ }
+
+ void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ return;
+ }
+
+ final float progress = backEvent.getProgress();
+
+ float springProgress = (progress > 0.1f
+ ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
+ : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+
+ mProgressSpring.animateToFinalPosition(springProgress);
+ }
+
+ static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+ }
+
+ void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ finishAnimation();
+ return;
+ }
+ mProgressSpring.cancel();
+
+ // Enter phase 2 of the animation
+ final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
+ .setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(mDecelerateInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = (float) animation.getAnimatedValue();
+ applyTransformTransaction(progress);
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ /**
+ * Load customize animation before animation start.
+ */
+ boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
+ if (result != null) {
+ mCloseAnimation = result.mCloseAnimation;
+ mEnterAnimation = result.mEnterAnimation;
+ mNextBackgroundColor = result.mBackgroundColor;
+ return true;
+ }
+ return false;
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackMotionEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CustomizeActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+ if (mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "No animation loaded, should choose cross-activity animation?");
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+
+
+ static final class AnimationLoadResult {
+ Animation mCloseAnimation;
+ Animation mEnterAnimation;
+ int mBackgroundColor;
+ }
+
+ /**
+ * Helper class to load custom animation.
+ */
+ static class CustomAnimationLoader {
+ final TransitionAnimation mTransitionAnimation;
+
+ CustomAnimationLoader(Context context) {
+ mTransitionAnimation = new TransitionAnimation(
+ context, false /* debug */, "CustomizeBackAnimation");
+ }
+
+ /**
+ * Load both enter and exit animation for the close activity transition.
+ * Note that the result is only valid if the exit animation has set and loaded success.
+ * If the entering animation has not set(i.e. 0), here will load the default entering
+ * animation for it.
+ *
+ * @param animationInfo The information of customize animation, which can be set from
+ * {@link Activity#overrideActivityTransition} and/or
+ * {@link LayoutParams#windowAnimations}
+ */
+ AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ if (animationInfo.getPackageName().isEmpty()) {
+ return null;
+ }
+ final Animation close = loadAnimation(animationInfo, false);
+ if (close == null) {
+ return null;
+ }
+ final Animation open = loadAnimation(animationInfo, true);
+ AnimationLoadResult result = new AnimationLoadResult();
+ result.mCloseAnimation = close;
+ result.mEnterAnimation = open;
+ result.mBackgroundColor = animationInfo.getCustomBackground();
+ return result;
+ }
+
+ /**
+ * Load enter or exit animation from CustomAnimationInfo
+ * @param animationInfo The information for customize animation.
+ * @param enterAnimation true when load for enter animation, false for exit animation.
+ * @return Loaded animation.
+ */
+ @Nullable
+ Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
+ boolean enterAnimation) {
+ Animation a = null;
+ // Activity#overrideActivityTransition has higher priority than windowAnimations
+ // Try to get animation from Activity#overrideActivityTransition
+ if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
+ || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
+ a = mTransitionAnimation.loadAppTransitionAnimation(
+ animationInfo.getPackageName(),
+ enterAnimation ? animationInfo.getCustomEnterAnim()
+ : animationInfo.getCustomExitAnim());
+ } else if (animationInfo.getWindowAnimations() != 0) {
+ // try to get animation from LayoutParams#windowAnimations
+ a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
+ animationInfo.getWindowAnimations(), enterAnimation
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation,
+ false /* translucent */);
+ }
+ // Only allow to load default animation for opening target.
+ if (a == null && enterAnimation) {
+ a = loadDefaultOpenAnimation();
+ }
+ if (a != null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
+ } else {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
+ }
+ return a;
+ }
+
+ private Animation loadDefaultOpenAnimation() {
+ return mTransitionAnimation.loadDefaultAnimationAttr(
+ R.styleable.WindowAnimation_activityCloseEnterAnimation,
+ false /* translucent */);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
index 2b2a0e397792..1a35de47e977 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
@@ -16,8 +16,9 @@
package com.android.wm.shell.back;
-import android.window.IOnBackInvokedCallback;
+import com.android.internal.view.AppearanceRegion;
import android.view.IRemoteAnimationRunner;
+import android.window.IOnBackInvokedCallback;
/**
* Interface for Launcher process to register back invocation callbacks.
@@ -34,4 +35,9 @@ interface IBackAnimation {
* Clears the previously registered {@link IOnBackInvokedCallback}.
*/
void clearBackToLauncherCallback();
+
+ /**
+ * Uses launcher flags to update the system bar color.
+ */
+ void customizeStatusBarAppearance(in AppearanceRegion appearance);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/StatusBarCustomizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/StatusBarCustomizer.java
new file mode 100644
index 000000000000..5e876127e9b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/StatusBarCustomizer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import com.android.internal.view.AppearanceRegion;
+
+/**
+ * Interface to customize the system bar color.
+ */
+public interface StatusBarCustomizer {
+ /**
+ * Called when the status bar color needs to be changed.
+ * @param appearance The region of appearance.
+ */
+ void customizeStatusBarAppearance(AppearanceRegion appearance);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
new file mode 100644
index 000000000000..837d5ff3b073
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+ "presubmit": [
+ {
+ "name": "WMShellUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "com.android.wm.shell.back"
+ }
+ ]
+ },
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackGestureInvokedTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackNavigationTests"
+ },
+ {
+ "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 695ef4e66302..7a00f5b9bab4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -16,7 +16,10 @@
package com.android.wm.shell.back;
+import android.annotation.FloatRange;
import android.os.SystemProperties;
+import android.util.MathUtils;
+import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -42,11 +45,13 @@ class TouchTracker {
*/
private float mInitTouchX;
private float mInitTouchY;
+ private float mLatestVelocityX;
+ private float mLatestVelocityY;
private float mStartThresholdX;
private int mSwipeEdge;
private boolean mCancelled;
- void update(float touchX, float touchY) {
+ void update(float touchX, float touchY, float velocityX, float velocityY) {
/**
* If back was previously cancelled but the user has started swiping in the forward
* direction again, restart back.
@@ -58,6 +63,8 @@ class TouchTracker {
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
+ mLatestVelocityX = velocityX;
+ mLatestVelocityY = velocityY;
}
void setTriggerBack(boolean triggerBack) {
@@ -84,34 +91,62 @@ class TouchTracker {
}
BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ return new BackMotionEvent(
+ /* touchX = */ mInitTouchX,
+ /* touchY = */ mInitTouchY,
+ /* progress = */ 0,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* swipeEdge = */ mSwipeEdge,
+ /* departingAnimationTarget = */ target);
}
BackMotionEvent createProgressEvent() {
- float progressThreshold = PROGRESS_THRESHOLD >= 0
- ? PROGRESS_THRESHOLD : mProgressThreshold;
- progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
float progress = 0;
// Progress is always 0 when back is cancelled and not restarted.
if (!mCancelled) {
- // If back is committed, progress is the distance between the last and first touch
- // point, divided by the max drag distance. Otherwise, it's the distance between
- // the last touch point and the starting threshold, divided by max drag distance.
- // The starting threshold is initially the first touch location, and updated to
- // the location everytime back is restarted after being cancelled.
- float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
- float deltaX = Math.max(
- mSwipeEdge == BackEvent.EDGE_LEFT
- ? mLatestTouchX - startX
- : startX - mLatestTouchX,
- 0);
- progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
+ progress = getProgress(mLatestTouchX);
}
return createProgressEvent(progress);
}
+ /**
+ * Progress value computed from the touch position.
+ *
+ * @param touchX the X touch position of the {@link MotionEvent}.
+ * @return progress value
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ float getProgress(float touchX) {
+ // If back is committed, progress is the distance between the last and first touch
+ // point, divided by the max drag distance. Otherwise, it's the distance between
+ // the last touch point and the starting threshold, divided by max drag distance.
+ // The starting threshold is initially the first touch location, and updated to
+ // the location everytime back is restarted after being cancelled.
+ float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
+ float deltaX = Math.abs(startX - touchX);
+ float maxX = getMaxX();
+ maxX = maxX == 0 ? 1 : maxX;
+ return MathUtils.constrain(deltaX / maxX, 0, 1);
+ }
+
+ /**
+ * Maximum X value (in pixels).
+ * Progress is considered to be completed (1f) when this limit is exceeded.
+ */
+ float getMaxX() {
+ return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
+ }
+
BackMotionEvent createProgressEvent(float progress) {
- return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ return new BackMotionEvent(
+ /* touchX = */ mLatestTouchX,
+ /* touchY = */ mLatestTouchY,
+ /* progress = */ progress,
+ /* velocityX = */ mLatestVelocityX,
+ /* velocityY = */ mLatestVelocityY,
+ /* swipeEdge = */ mSwipeEdge,
+ /* departingAnimationTarget = */ null);
}
public void setProgressThreshold(float progressThreshold) {
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 e24c2286013d..d3f395846894 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
@@ -47,6 +47,10 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
import java.io.PrintWriter;
import java.util.List;
@@ -60,7 +64,11 @@ import java.util.concurrent.Executor;
public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
- public static final String KEY_APP_BUBBLE = "key_app_bubble";
+ /** A string suffix used in app bubbles' {@link #mKey}. */
+ private static final String KEY_APP_BUBBLE = "key_app_bubble";
+
+ /** Whether the bubble is an app bubble. */
+ private final boolean mIsAppBubble;
private final String mKey;
@Nullable
@@ -86,8 +94,18 @@ public class Bubble implements BubbleViewProvider {
private String mAppName;
private ShortcutInfo mShortcutInfo;
private String mMetadataShortcutId;
+
+ /**
+ * If {@link BubbleController#isShowingAsBubbleBar()} is true, the only view that will be
+ * populated will be {@link #mBubbleBarExpandedView}. If it is false, {@link #mIconView}
+ * and {@link #mExpandedView} will be populated.
+ */
+ @Nullable
private BadgedImageView mIconView;
+ @Nullable
private BubbleExpandedView mExpandedView;
+ @Nullable
+ private BubbleBarExpandedView mBubbleBarExpandedView;
private BubbleViewInfoTask mInflationTask;
private boolean mInflateSynchronously;
@@ -167,7 +185,7 @@ public class Bubble implements BubbleViewProvider {
private PendingIntent mDeleteIntent;
/**
- * Used only for a special bubble in the stack that has the key {@link #KEY_APP_BUBBLE}.
+ * Used only for a special bubble in the stack that has {@link #mIsAppBubble} set to true.
* There can only be one of these bubbles in the stack and this intent will be populated for
* that bubble.
*/
@@ -202,22 +220,54 @@ public class Bubble implements BubbleViewProvider {
mMainExecutor = mainExecutor;
mTaskId = taskId;
mBubbleMetadataFlagListener = listener;
+ mIsAppBubble = false;
}
- public Bubble(Intent intent,
+ private Bubble(
+ Intent intent,
UserHandle user,
+ @Nullable Icon icon,
+ boolean isAppBubble,
+ String key,
Executor mainExecutor) {
- mKey = KEY_APP_BUBBLE;
mGroupKey = null;
mLocusId = null;
mFlags = 0;
mUser = user;
+ mIcon = icon;
+ mIsAppBubble = isAppBubble;
+ mKey = key;
mShowBubbleUpdateDot = false;
mMainExecutor = mainExecutor;
mTaskId = INVALID_TASK_ID;
mAppIntent = intent;
mDesiredHeight = Integer.MAX_VALUE;
mPackageName = intent.getPackage();
+
+ }
+
+ /** Creates an app bubble. */
+ public static Bubble createAppBubble(
+ Intent intent,
+ UserHandle user,
+ @Nullable Icon icon,
+ Executor mainExecutor) {
+ return new Bubble(intent,
+ user,
+ icon,
+ /* isAppBubble= */ true,
+ /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user),
+ mainExecutor);
+ }
+
+ /**
+ * Returns the key for an app bubble from an app with package name, {@code packageName} on an
+ * Android user, {@code user}.
+ */
+ public static String getAppBubbleKeyForApp(String packageName, UserHandle user) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(user);
+ return KEY_APP_BUBBLE + ":" + user.getIdentifier() + ":" + packageName;
}
@VisibleForTesting(visibility = PRIVATE)
@@ -225,6 +275,7 @@ public class Bubble implements BubbleViewProvider {
final Bubbles.BubbleMetadataFlagListener listener,
final Bubbles.PendingIntentCanceledListener intentCancelListener,
Executor mainExecutor) {
+ mIsAppBubble = false;
mKey = entry.getKey();
mGroupKey = entry.getGroupKey();
mLocusId = entry.getLocusId();
@@ -242,6 +293,16 @@ public class Bubble implements BubbleViewProvider {
setEntry(entry);
}
+ /** Converts this bubble into a {@link BubbleInfo} object to be shared with external callers. */
+ public BubbleInfo asBubbleBarBubble() {
+ return new BubbleInfo(getKey(),
+ getFlags(),
+ getShortcutInfo().getId(),
+ getIcon(),
+ getUser().getIdentifier(),
+ getPackageName());
+ }
+
@Override
public String getKey() {
return mKey;
@@ -314,13 +375,19 @@ public class Bubble implements BubbleViewProvider {
return mIconView;
}
- @Override
@Nullable
+ @Override
public BubbleExpandedView getExpandedView() {
return mExpandedView;
}
@Nullable
+ @Override
+ public BubbleBarExpandedView getBubbleBarExpandedView() {
+ return mBubbleBarExpandedView;
+ }
+
+ @Nullable
public String getTitle() {
return mTitle;
}
@@ -351,6 +418,9 @@ public class Bubble implements BubbleViewProvider {
mExpandedView.cleanUpExpandedState();
mExpandedView = null;
}
+ if (mBubbleBarExpandedView != null) {
+ mBubbleBarExpandedView.cleanUpExpandedState();
+ }
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
}
@@ -397,16 +467,16 @@ public class Bubble implements BubbleViewProvider {
* @param callback the callback to notify one the bubble is ready to be displayed.
* @param context the context for the bubble.
* @param controller the bubble controller.
- * @param stackView the stackView the bubble is eventually added to.
+ * @param stackView the view the bubble is added to, iff showing as floating.
+ * @param layerView the layer the bubble is added to, iff showing in the bubble bar.
* @param iconFactory the icon factory use to create images for the bubble.
- * @param badgeIconFactory the icon factory to create app badges for the bubble.
*/
void inflate(BubbleViewInfoTask.Callback callback,
Context context,
BubbleController controller,
- BubbleStackView stackView,
+ @Nullable BubbleStackView stackView,
+ @Nullable BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
- BubbleBadgeIconFactory badgeIconFactory,
boolean skipInflation) {
if (isBubbleLoading()) {
mInflationTask.cancel(true /* mayInterruptIfRunning */);
@@ -415,8 +485,8 @@ public class Bubble implements BubbleViewProvider {
context,
controller,
stackView,
+ layerView,
iconFactory,
- badgeIconFactory,
skipInflation,
callback,
mMainExecutor);
@@ -432,7 +502,7 @@ public class Bubble implements BubbleViewProvider {
}
boolean isInflated() {
- return mIconView != null && mExpandedView != null;
+ return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null;
}
void stopInflation() {
@@ -446,6 +516,7 @@ public class Bubble implements BubbleViewProvider {
if (!isInflated()) {
mIconView = info.imageView;
mExpandedView = info.expandedView;
+ mBubbleBarExpandedView = info.bubbleBarExpandedView;
}
mShortcutInfo = info.shortcutInfo;
@@ -456,7 +527,7 @@ public class Bubble implements BubbleViewProvider {
mFlyoutMessage = info.flyoutMessage;
mBadgeBitmap = info.badgeBitmap;
- mRawBadgeBitmap = info.mRawBadgeBitmap;
+ mRawBadgeBitmap = info.rawBadgeBitmap;
mBubbleBitmap = info.bubbleBitmap;
mDotColor = info.dotColor;
@@ -465,6 +536,9 @@ public class Bubble implements BubbleViewProvider {
if (mExpandedView != null) {
mExpandedView.update(this /* bubble */);
}
+ if (mBubbleBarExpandedView != null) {
+ mBubbleBarExpandedView.update(this /* bubble */);
+ }
if (mIconView != null) {
mIconView.setRenderedBubble(this /* bubble */);
}
@@ -543,8 +617,13 @@ public class Bubble implements BubbleViewProvider {
}
}
+ /**
+ * @return the icon set on BubbleMetadata, if it exists. This is only non-null for bubbles
+ * created via a PendingIntent. This is null for bubbles created by a shortcut, as we use the
+ * icon from the shortcut.
+ */
@Nullable
- Icon getIcon() {
+ public Icon getIcon() {
return mIcon;
}
@@ -589,6 +668,9 @@ public class Bubble implements BubbleViewProvider {
*/
@Override
public int getTaskId() {
+ if (mBubbleBarExpandedView != null) {
+ return mBubbleBarExpandedView.getTaskId();
+ }
return mExpandedView != null ? mExpandedView.getTaskId() : mTaskId;
}
@@ -638,6 +720,13 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Whether this bubble is conversation
+ */
+ public boolean isConversation() {
+ return null != mShortcutInfo;
+ }
+
+ /**
* Sets whether this notification should be suppressed in the shade.
*/
@VisibleForTesting
@@ -761,7 +850,7 @@ public class Bubble implements BubbleViewProvider {
}
boolean isAppBubble() {
- return KEY_APP_BUBBLE.equals(mKey);
+ return mIsAppBubble;
}
Intent getSettingsIntent(final Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
deleted file mode 100644
index 56b13b8dcd46..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ /dev/null
@@ -1,120 +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.bubbles;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating app badge icons that are shown on bubbles.
- */
-public class BubbleBadgeIconFactory extends BaseIconFactory {
-
- public BubbleBadgeIconFactory(Context context) {
- super(context, context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size));
- }
-
- /**
- * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
- * will include the workprofile indicator on the badge if appropriate.
- */
- BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
- if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
- AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon;
- userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(), ad.getForeground());
- }
- if (isImportantConversation) {
- userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
- }
- Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1, MODE_WITH_SHADOW);
- return createIconBitmap(userBadgedBitmap);
- }
-
- private class CircularRingDrawable extends CircularAdaptiveIcon {
-
- final int mImportantConversationColor;
- final int mRingWidth;
- final Rect mInnerBounds = new Rect();
-
- final Drawable mDr;
-
- CircularRingDrawable(Drawable dr) {
- super(null, null);
- mDr = dr;
- mImportantConversationColor = mContext.getResources().getColor(
- R.color.important_conversation, null);
- mRingWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width);
- }
-
- @Override
- public void draw(Canvas canvas) {
- int save = canvas.save();
- canvas.clipPath(getIconMask());
- canvas.drawColor(mImportantConversationColor);
- mInnerBounds.set(getBounds());
- mInnerBounds.inset(mRingWidth, mRingWidth);
- canvas.translate(mInnerBounds.left, mInnerBounds.top);
- mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
- mDr.draw(canvas);
- canvas.restoreToCount(save);
- }
- }
-
- private static class CircularAdaptiveIcon extends AdaptiveIconDrawable {
-
- final Path mPath = new Path();
-
- CircularAdaptiveIcon(Drawable bg, Drawable fg) {
- super(bg, fg);
- }
-
- @Override
- public Path getIconMask() {
- mPath.reset();
- Rect bounds = getBounds();
- mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
- return mPath;
- }
-
- @Override
- public void draw(Canvas canvas) {
- int save = canvas.save();
- canvas.clipPath(getIconMask());
-
- Drawable d;
- if ((d = getBackground()) != null) {
- d.draw(canvas);
- }
- if ((d = getForeground()) != null) {
- d.draw(canvas);
- }
- canvas.restoreToCount(save);
- }
- }
-}
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 36c0cb6dfe19..21f02b10035b 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
@@ -24,7 +24,6 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -38,7 +37,9 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
+import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -57,10 +58,13 @@ import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.drawable.Icon;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
@@ -69,8 +73,10 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.IWindowManager;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ScreenCapture;
@@ -82,17 +88,23 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -101,6 +113,8 @@ import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -121,7 +135,8 @@ import java.util.function.IntConsumer;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener {
+public class BubbleController implements ConfigurationChangeListener,
+ RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -129,6 +144,39 @@ public class BubbleController implements ConfigurationChangeListener {
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ // TODO(b/256873975) Should use proper flag when available to shell/launcher
+ /**
+ * Whether bubbles are showing in the bubble bar from launcher. This is only available
+ * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
+ * to check all conditions that indicate if the bubble bar is in use.
+ */
+ private static final boolean BUBBLE_BAR_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
+
+ /**
+ * Common interface to send updates to bubble views.
+ */
+ public interface BubbleViewCallback {
+ /** Called when the provided bubble should be removed. */
+ void removeBubble(Bubble removedBubble);
+ /** Called when the provided bubble should be added. */
+ void addBubble(Bubble addedBubble);
+ /** Called when the provided bubble should be updated. */
+ void updateBubble(Bubble updatedBubble);
+ /** Called when the provided bubble should be selected. */
+ void selectionChanged(BubbleViewProvider selectedBubble);
+ /** Called when the provided bubble's suppression state has changed. */
+ void suppressionChanged(Bubble bubble, boolean isSuppressed);
+ /** Called when the expansion state of bubbles has changed. */
+ void expansionChanged(boolean isExpanded);
+ /**
+ * Called when the order of the bubble list has changed. Depending on the expanded state
+ * the pointer might need to be updated.
+ */
+ void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
+ }
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -152,17 +200,13 @@ public class BubbleController implements ConfigurationChangeListener {
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
-
private final ShellExecutor mBackgroundExecutor;
- // Whether or not we should show bubbles pinned at the bottom of the screen.
- private boolean mIsBubbleBarEnabled;
-
private BubbleLogger mLogger;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
+ @Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubbleBadgeIconFactory mBubbleBadgeIconFactory;
private BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
@@ -217,6 +261,11 @@ public class BubbleController implements ConfigurationChangeListener {
private Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
+ /** Used to send bubble events to launcher. */
+ private Bubbles.BubbleStateListener mBubbleStateListener;
+
+ /** Used to send updates to the views from {@link #mBubbleDataListener}. */
+ private BubbleViewCallback mBubbleViewCallback;
public BubbleController(Context context,
ShellInit shellInit,
@@ -268,8 +317,12 @@ public class BubbleController implements ConfigurationChangeListener {
mBubblePositioner = positioner;
mBubbleData = data;
mSavedUserBubbleData = new SparseArray<>();
- mBubbleIconFactory = new BubbleIconFactory(context);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
+ mBubbleIconFactory = new BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
mOneHandedOptional = oneHandedOptional;
@@ -299,6 +352,9 @@ public class BubbleController implements ConfigurationChangeListener {
}
protected void onInit() {
+ mBubbleViewCallback = isShowingAsBubbleBar()
+ ? mBubbleBarViewCallback
+ : mBubbleStackViewCallback;
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
mDataRepository.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
@@ -427,9 +483,15 @@ public class BubbleController implements ConfigurationChangeListener {
mCurrentProfiles = userProfiles;
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new BubbleController.IBubblesImpl(this);
+ }
+
@VisibleForTesting
public Bubbles asBubbles() {
return mImpl;
@@ -444,6 +506,48 @@ public class BubbleController implements ConfigurationChangeListener {
return mMainExecutor;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Sets a listener to be notified of bubble updates. This is used by launcher so that
+ * it may render bubbles in itself. Only one listener is supported.
+ */
+ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+ if (isShowingAsBubbleBar()) {
+ // Only set the listener if bubble bar is showing.
+ mBubbleStateListener = listener;
+ sendInitialListenerUpdate();
+ } else {
+ mBubbleStateListener = null;
+ }
+ }
+
+ /**
+ * Unregisters the {@link Bubbles.BubbleStateListener}.
+ */
+ public void unregisterBubbleStateListener() {
+ mBubbleStateListener = null;
+ }
+
+ /**
+ * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble
+ * state to it.
+ */
+ private void sendInitialListenerUpdate() {
+ if (mBubbleStateListener != null) {
+ BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+ mBubbleStateListener.onBubbleStateChange(update);
+ }
+ }
+
/**
* Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
*/
@@ -456,7 +560,7 @@ public class BubbleController implements ConfigurationChangeListener {
}
private void openBubbleOverflow() {
- ensureStackViewCreated();
+ ensureBubbleViewsAndWindowCreated();
mBubbleData.setShowingOverflow(true);
mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
mBubbleData.setExpanded(true);
@@ -496,7 +600,7 @@ public class BubbleController implements ConfigurationChangeListener {
expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
}
- updateStack();
+ updateBubbleViews();
}
@VisibleForTesting
@@ -540,10 +644,10 @@ public class BubbleController implements ConfigurationChangeListener {
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
- // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
- /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
- public void setBubbleBarEnabled(boolean enabled) {
- mIsBubbleBarEnabled = enabled;
+ /** Whether bubbles are showing in the bubble bar. */
+ public boolean isShowingAsBubbleBar() {
+ // TODO(b/269670598): should also check that we're in gesture nav
+ return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
}
/** Whether this userId belongs to the current user. */
@@ -592,45 +696,63 @@ public class BubbleController implements ConfigurationChangeListener {
return mBubblePositioner;
}
- Bubbles.SysuiProxy getSysuiProxy() {
+ BubbleIconFactory getIconFactory() {
+ return mBubbleIconFactory;
+ }
+
+ public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
/**
- * BubbleStackView is lazily created by this method the first time a Bubble is added. This
- * method initializes the stack view and adds it to window manager.
+ * The view and window for bubbles is lazily created by this method the first time a Bubble
+ * is added. Depending on the device state, this method will:
+ * - initialize a {@link BubbleStackView} and add it to window manager OR
+ * - initialize a {@link com.android.wm.shell.bubbles.bar.BubbleBarLayerView} and adds
+ * it to window manager.
*/
- private void ensureStackViewCreated() {
- if (mStackView == null) {
- mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- mMainExecutor);
- mStackView.onOrientationChanged();
- if (mExpandListener != null) {
- mStackView.setExpandListener(mExpandListener);
+ private void ensureBubbleViewsAndWindowCreated() {
+ mBubblePositioner.setShowingInBubbleBar(isShowingAsBubbleBar());
+ if (isShowingAsBubbleBar()) {
+ // When we're showing in launcher / bubble bar is enabled, we don't have bubble stack
+ // view, instead we just show the expanded bubble view as necessary. We still need a
+ // window to show this in, but we use a separate code path.
+ // TODO(b/273312602): consider foldables where we do need a stack view when folded
+ if (mLayerView == null) {
+ mLayerView = new BubbleBarLayerView(mContext, this);
}
- mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
- }
-
- if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
- mBubblePositioner.setUsePinnedLocation(true);
} else {
- mBubblePositioner.setUsePinnedLocation(false);
+ if (mStackView == null) {
+ mStackView = new BubbleStackView(
+ mContext, this, mBubbleData, mSurfaceSynchronizer,
+ mFloatingContentCoordinator,
+ mMainExecutor);
+ mStackView.onOrientationChanged();
+ if (mExpandListener != null) {
+ mStackView.setExpandListener(mExpandListener);
+ }
+ mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
+ }
}
-
addToWindowManagerMaybe();
}
- /** Adds the BubbleStackView to the WindowManager if it's not already there. */
+ /** Adds the appropriate view to WindowManager if it's not already there. */
private void addToWindowManagerMaybe() {
- // If the stack is null, or already added, don't add it.
- if (mStackView == null || mAddedToWindowManager) {
+ // If already added, don't add it.
+ if (mAddedToWindowManager) {
+ return;
+ }
+ // If the appropriate view is null, don't add it.
+ if (isShowingAsBubbleBar() && mLayerView == null) {
+ return;
+ } else if (!isShowingAsBubbleBar() && mStackView == null) {
return;
}
mWmLayoutParams = new WindowManager.LayoutParams(
// Fill the screen so we can use translation animations to position the bubble
- // stack. We'll use touchable regions to ignore touches that are not on the bubbles
+ // views. We'll use touchable regions to ignore touches that are not on the bubbles
// themselves.
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -653,17 +775,30 @@ public class BubbleController implements ConfigurationChangeListener {
mAddedToWindowManager = true;
registerBroadcastReceiver();
mBubbleData.getOverflow().initialize(this);
- mWindowManager.addView(mStackView, mWmLayoutParams);
- mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
- if (!windowInsets.equals(mWindowInsets)) {
- mWindowInsets = windowInsets;
- mBubblePositioner.update();
- mStackView.onDisplaySizeChanged();
- }
- return windowInsets;
- });
+ // (TODO: b/273314541) some duplication in the inset listener
+ if (isShowingAsBubbleBar()) {
+ mWindowManager.addView(mLayerView, mWmLayoutParams);
+ mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ mBubblePositioner.update();
+ mLayerView.onDisplaySizeChanged();
+ }
+ return windowInsets;
+ });
+ } else {
+ mWindowManager.addView(mStackView, mWmLayoutParams);
+ mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ mBubblePositioner.update();
+ mStackView.onDisplaySizeChanged();
+ }
+ return windowInsets;
+ });
+ }
} catch (IllegalStateException e) {
- // This means the stack has already been added. This shouldn't happen...
+ // This means the view has already been added. This shouldn't happen...
e.printStackTrace();
}
}
@@ -686,7 +821,7 @@ public class BubbleController implements ConfigurationChangeListener {
}
}
- /** Removes the BubbleStackView from the WindowManager if it's there. */
+ /** Removes any bubble views from the WindowManager that exist. */
private void removeFromWindowManagerMaybe() {
if (!mAddedToWindowManager) {
return;
@@ -707,8 +842,10 @@ public class BubbleController implements ConfigurationChangeListener {
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mBubbleData.getOverflow().cleanUpExpandedState();
- } else {
- Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
+ }
+ if (mLayerView != null) {
+ mWindowManager.removeView(mLayerView);
+ mBubbleData.getOverflow().cleanUpExpandedState();
}
} catch (IllegalArgumentException e) {
// This means the stack has already been removed - it shouldn't happen, but ignore if it
@@ -798,18 +935,30 @@ public class BubbleController implements ConfigurationChangeListener {
if (mStackView != null) {
mStackView.onThemeChanged();
}
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
// Reload each bubble
for (Bubble b : mBubbleData.getBubbles()) {
- b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
- mBubbleBadgeIconFactory,
+ b.inflate(null /* callback */,
+ mContext,
+ this,
+ mStackView,
+ mLayerView,
+ mBubbleIconFactory,
false /* skipInflation */);
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
- b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
- mBubbleBadgeIconFactory,
+ b.inflate(null /* callback */,
+ mContext,
+ this,
+ mStackView,
+ mLayerView,
+ mBubbleIconFactory,
false /* skipInflation */);
}
}
@@ -825,8 +974,12 @@ public class BubbleController implements ConfigurationChangeListener {
mDensityDpi = newConfig.densityDpi;
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
mBubbleData.onMaxBubblesChanged();
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
}
if (newConfig.fontScale != mFontScale) {
@@ -875,7 +1028,7 @@ public class BubbleController implements ConfigurationChangeListener {
*/
@VisibleForTesting
public boolean hasBubbles() {
- if (mStackView == null) {
+ if (mStackView == null && mLayerView == null) {
return false;
}
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
@@ -913,7 +1066,27 @@ public class BubbleController implements ConfigurationChangeListener {
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow.
*
- * This is currently only used when opening a bubble via clicking on a conversation widget.
+ * This is used by external callers (launcher).
+ */
+ public void expandStackAndSelectBubbleFromLauncher(String key) {
+ Bubble b = mBubbleData.getAnyBubbleWithkey(key);
+ if (b == null) {
+ return;
+ }
+ if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
+ // already in the stack
+ mBubbleData.setSelectedBubbleFromLauncher(b);
+ mLayerView.showExpandedView(b);
+ } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
+ // TODO: (b/271468319) handle overflow
+ } else {
+ Log.w(TAG, "didn't add bubble from launcher: " + key);
+ }
+ }
+
+ /**
+ * Expands and selects the provided bubble as long as it already exists in the stack or the
+ * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
*/
public void expandStackAndSelectBubble(Bubble b) {
if (b == null) {
@@ -1004,26 +1177,30 @@ public class BubbleController implements ConfigurationChangeListener {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
- public void showOrHideAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
if (intent == null || intent.getPackage() == null) {
Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
return;
}
- PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
- if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
+ String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
+ if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
- Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
+ Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey);
if (existingAppBubble != null) {
BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
if (isStackExpanded()) {
- if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) {
+ if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
// App bubble is expanded, lets collapse
collapseStack();
} else {
@@ -1037,7 +1214,7 @@ public class BubbleController implements ConfigurationChangeListener {
}
} else {
// App bubble does not exist, lets add and expand it
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1046,21 +1223,32 @@ public class BubbleController implements ConfigurationChangeListener {
/**
* Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
* can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
- *
- * TODO(b/267324693): Implement the exclude layer functionality in screenshot.
*/
public void getScreenshotExcludingBubble(int displayId,
Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener) {
try {
- mWmService.captureDisplay(displayId, null, screenCaptureListener.first);
+ ScreenCapture.CaptureArgs args = null;
+ if (mStackView != null) {
+ ViewRootImpl viewRoot = mStackView.getViewRootImpl();
+ if (viewRoot != null) {
+ SurfaceControl bubbleLayer = viewRoot.getSurfaceControl();
+ if (bubbleLayer != null) {
+ args = new ScreenCapture.CaptureArgs.Builder<>()
+ .setExcludeLayers(new SurfaceControl[] {bubbleLayer})
+ .build();
+ }
+ }
+ }
+
+ mWmService.captureDisplay(displayId, args, screenCaptureListener.first);
} catch (RemoteException e) {
Log.e(TAG, "Failed to capture screenshot");
}
}
/** Sets the app bubble's taskId which is cached for SysUI. */
- public void setAppBubbleTaskId(int taskId) {
- mImpl.mCachedState.setAppBubbleTaskId(taskId);
+ public void setAppBubbleTaskId(String key, int taskId) {
+ mImpl.mCachedState.setAppBubbleTaskId(key, taskId);
}
/**
@@ -1079,7 +1267,11 @@ public class BubbleController implements ConfigurationChangeListener {
}
bubble.inflate(
(b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
- mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
+ mContext,
+ this,
+ mStackView,
+ mLayerView,
+ mBubbleIconFactory,
true /* skipInflation */);
});
return null;
@@ -1151,10 +1343,11 @@ public class BubbleController implements ConfigurationChangeListener {
@VisibleForTesting
public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
// Lazy init stack view when a bubble is created
- ensureStackViewCreated();
+ ensureBubbleViewsAndWindowCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
- mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
+ mContext, this, mStackView, mLayerView,
+ mBubbleIconFactory,
false /* skipInflation */);
}
@@ -1319,6 +1512,115 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
+ /** When bubbles are floating, this will be used to notify the floating views. */
+ private final BubbleViewCallback mBubbleStackViewCallback = new BubbleViewCallback() {
+ @Override
+ public void removeBubble(Bubble removedBubble) {
+ if (mStackView != null) {
+ mStackView.removeBubble(removedBubble);
+ }
+ }
+
+ @Override
+ public void addBubble(Bubble addedBubble) {
+ if (mStackView != null) {
+ mStackView.addBubble(addedBubble);
+ }
+ }
+
+ @Override
+ public void updateBubble(Bubble updatedBubble) {
+ if (mStackView != null) {
+ mStackView.updateBubble(updatedBubble);
+ }
+ }
+
+ @Override
+ public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
+ if (mStackView != null) {
+ mStackView.updateBubbleOrder(bubbleOrder, updatePointer);
+ }
+ }
+
+ @Override
+ public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
+ if (mStackView != null) {
+ mStackView.setBubbleSuppressed(bubble, isSuppressed);
+ }
+ }
+
+ @Override
+ public void expansionChanged(boolean isExpanded) {
+ if (mStackView != null) {
+ mStackView.setExpanded(isExpanded);
+ }
+ }
+
+ @Override
+ public void selectionChanged(BubbleViewProvider selectedBubble) {
+ if (mStackView != null) {
+ mStackView.setSelectedBubble(selectedBubble);
+ }
+
+ }
+ };
+
+ /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */
+ private final BubbleViewCallback mBubbleBarViewCallback = new BubbleViewCallback() {
+ @Override
+ public void removeBubble(Bubble removedBubble) {
+ if (mLayerView != null) {
+ // TODO: need to check if there's something that needs to happen here, e.g. if
+ // the currently selected & expanded bubble is removed?
+ }
+ }
+
+ @Override
+ public void addBubble(Bubble addedBubble) {
+ // Nothing to do for adds, these are handled by launcher / in the bubble bar.
+ }
+
+ @Override
+ public void updateBubble(Bubble updatedBubble) {
+ // Nothing to do for updates, these are handled by launcher / in the bubble bar.
+ }
+
+ @Override
+ public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
+ // Nothing to do for order changes, these are handled by launcher / in the bubble bar.
+ }
+
+ @Override
+ public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
+ if (mLayerView != null) {
+ // TODO (b/273316505) handle suppression changes, although might not need to
+ // to do anything on the layerview side for this...
+ }
+ }
+
+ @Override
+ public void expansionChanged(boolean isExpanded) {
+ if (mLayerView != null) {
+ if (!isExpanded) {
+ mLayerView.collapse();
+ } else {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (selectedBubble != null) {
+ mLayerView.showExpandedView(selectedBubble);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void selectionChanged(BubbleViewProvider selectedBubble) {
+ // Only need to update the layer view if we're currently expanded for selection changes.
+ if (mLayerView != null && isStackExpanded()) {
+ mLayerView.showExpandedView(selectedBubble);
+ }
+ }
+ };
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@@ -1336,12 +1638,13 @@ public class BubbleController implements ConfigurationChangeListener {
+ " unsuppressed=" + (update.unsuppressedBubble != null));
}
- ensureStackViewCreated();
+ ensureBubbleViewsAndWindowCreated();
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
- mStackView.updateOverflowButtonDot();
+ // If bubbles in the overflow have a dot, make sure the overflow shows a dot
+ updateOverflowButtonDot();
// Update bubbles in overflow.
if (mOverflowListener != null) {
@@ -1356,9 +1659,7 @@ public class BubbleController implements ConfigurationChangeListener {
final Bubble bubble = removed.first;
@Bubbles.DismissReason final int reason = removed.second;
- if (mStackView != null) {
- mStackView.removeBubble(bubble);
- }
+ mBubbleViewCallback.removeBubble(bubble);
// Leave the notification in place if we're dismissing due to user switching, or
// because DND is suppressing the bubble. In both of those cases, we need to be able
@@ -1388,59 +1689,78 @@ public class BubbleController implements ConfigurationChangeListener {
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
- if (update.addedBubble != null && mStackView != null) {
+ if (update.addedBubble != null) {
mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
- mStackView.addBubble(update.addedBubble);
+ mBubbleViewCallback.addBubble(update.addedBubble);
}
- if (update.updatedBubble != null && mStackView != null) {
- mStackView.updateBubble(update.updatedBubble);
+ if (update.updatedBubble != null) {
+ mBubbleViewCallback.updateBubble(update.updatedBubble);
}
- if (update.suppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+ if (update.suppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true);
}
- if (update.unsuppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+ if (update.unsuppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false);
}
boolean collapseStack = update.expandedChanged && !update.expanded;
// At this point, the correct bubbles are inflated in the stack.
// Make sure the order in bubble data is reflected in bubble row.
- if (update.orderChanged && mStackView != null) {
+ if (update.orderChanged) {
mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
// if the stack is going to be collapsed, do not update pointer position
// after reordering
- mStackView.updateBubbleOrder(update.bubbles, !collapseStack);
+ mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack);
}
if (collapseStack) {
- mStackView.setExpanded(false);
+ mBubbleViewCallback.expansionChanged(/* expanded= */ false);
mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
}
- if (update.selectionChanged && mStackView != null) {
- mStackView.setSelectedBubble(update.selectedBubble);
+ if (update.selectionChanged) {
+ mBubbleViewCallback.selectionChanged(update.selectedBubble);
}
// Expanding? Apply this last.
if (update.expandedChanged && update.expanded) {
- if (mStackView != null) {
- mStackView.setExpanded(true);
- mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
- }
+ mBubbleViewCallback.expansionChanged(/* expanded= */ true);
+ mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
}
mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
- updateStack();
+ updateBubbleViews();
// Update the cached state for queries from SysUI
mImpl.mCachedState.update(update);
+
+ if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+ BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
+ // Some updates aren't relevant to the bubble bar so check first.
+ if (bubbleBarUpdate.anythingChanged()) {
+ mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+ }
+ }
}
};
+ private void updateOverflowButtonDot() {
+ BubbleOverflow overflow = mBubbleData.getOverflow();
+ if (overflow == null) return;
+
+ for (Bubble b : mBubbleData.getOverflowBubbles()) {
+ if (b.showDot()) {
+ overflow.setShowDot(true);
+ return;
+ }
+ }
+ overflow.setShowDot(false);
+ }
+
private boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
if (isSummaryOfBubbles(entry)) {
@@ -1501,28 +1821,42 @@ public class BubbleController implements ConfigurationChangeListener {
/**
* Updates the visibility of the bubbles based on current state.
- * Does not un-bubble, just hides or un-hides.
- * Updates stack description for TalkBack focus.
- * Updates bubbles' icon views clickable states
+ * Does not un-bubble, just hides or un-hides the views themselves.
+ *
+ * Updates view description for TalkBack focus.
+ * Updates bubbles' icon views clickable states (when floating).
*/
- public void updateStack() {
- if (mStackView == null) {
+ public void updateBubbleViews() {
+ if (mStackView == null && mLayerView == null) {
return;
}
if (!mIsStatusBarShade) {
- // Bubbles don't appear over the locked shade.
- mStackView.setVisibility(INVISIBLE);
+ // Bubbles don't appear when the device is locked.
+ if (mStackView != null) {
+ mStackView.setVisibility(INVISIBLE);
+ }
+ if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ }
} else if (hasBubbles()) {
// If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the
// stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate
// out.
- mStackView.setVisibility(VISIBLE);
+ if (mStackView != null) {
+ mStackView.setVisibility(VISIBLE);
+ }
+ if (mLayerView != null && isStackExpanded()) {
+ mLayerView.setVisibility(VISIBLE);
+ }
}
- mStackView.updateContentDescription();
-
- mStackView.updateBubblesAcessibillityStates();
+ if (mStackView != null) {
+ mStackView.updateContentDescription();
+ mStackView.updateBubblesAcessibillityStates();
+ } else if (mLayerView != null) {
+ // TODO(b/273313561): handle a11y for BubbleBarLayerView
+ }
}
@VisibleForTesting
@@ -1558,7 +1892,7 @@ public class BubbleController implements ConfigurationChangeListener {
/**
* Whether an intent is properly configured to display in a
- * {@link com.android.wm.shell.TaskView}.
+ * {@link TaskView}.
*
* Keep checks in sync with BubbleExtractor#canLaunchInTaskView. Typically
* that should filter out any invalid bubbles, but should protect SysUI side just in case.
@@ -1632,6 +1966,76 @@ public class BubbleController implements ConfigurationChangeListener {
}
}
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder {
+ private BubbleController mController;
+ private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
+ private final Bubbles.BubbleStateListener mBubbleListener =
+ new Bubbles.BubbleStateListener() {
+
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
+ };
+
+ IBubblesImpl(BubbleController controller) {
+ mController = controller;
+ mListener = new SingleInstanceRemoteListener<>(mController,
+ c -> c.registerBubbleStateListener(mBubbleListener),
+ c -> c.unregisterBubbleStateListener());
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> {
+ mListener.register(listener);
+ });
+ }
+
+ @Override
+ public void unregisterBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> mListener.unregister());
+ }
+
+ @Override
+ public void showBubble(String key, boolean onLauncherHome) {
+ mMainExecutor.execute(() -> {
+ mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
+ mController.expandStackAndSelectBubbleFromLauncher(key);
+ });
+ }
+
+ @Override
+ public void removeBubble(String key, int reason) {
+ // TODO (b/271466616) allow removals from launcher
+ }
+
+ @Override
+ public void collapseBubbles() {
+ mMainExecutor.execute(() -> mController.collapseStack());
+ }
+
+ @Override
+ public void onTaskbarStateChanged(int newState) {
+ // TODO (b/269670598)
+ }
+ }
+
private class BubblesImpl implements Bubbles {
// Up-to-date cached state of bubbles data for SysUI to query from the calling thread
@VisibleForTesting
@@ -1641,7 +2045,8 @@ public class BubbleController implements ConfigurationChangeListener {
private HashSet<String> mSuppressedBubbleKeys = new HashSet<>();
private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
- private int mAppBubbleTaskId = INVALID_TASK_ID;
+
+ private HashMap<String, Integer> mAppBubbleTaskIds = new HashMap();
private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();
@@ -1673,20 +2078,20 @@ public class BubbleController implements ConfigurationChangeListener {
mSuppressedBubbleKeys.clear();
mShortcutIdToBubble.clear();
- mAppBubbleTaskId = INVALID_TASK_ID;
+ mAppBubbleTaskIds.clear();
for (Bubble b : mTmpBubbles) {
mShortcutIdToBubble.put(b.getShortcutId(), b);
updateBubbleSuppressedState(b);
- if (KEY_APP_BUBBLE.equals(b.getKey())) {
- mAppBubbleTaskId = b.getTaskId();
+ if (b.isAppBubble()) {
+ mAppBubbleTaskIds.put(b.getKey(), b.getTaskId());
}
}
}
/** Sets the app bubble's taskId which is cached for SysUI. */
- synchronized void setAppBubbleTaskId(int taskId) {
- mAppBubbleTaskId = taskId;
+ synchronized void setAppBubbleTaskId(String key, int taskId) {
+ mAppBubbleTaskIds.put(key, taskId);
}
/**
@@ -1739,12 +2144,23 @@ public class BubbleController implements ConfigurationChangeListener {
pw.println(" suppressing: " + key);
}
- pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId);
+ pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
}
}
private CachedState mCachedState = new CachedState();
+ private IBubblesImpl mIBubbles;
+
+ @Override
+ public IBubbles createExternalInterface() {
+ if (mIBubbles != null) {
+ mIBubbles.invalidate();
+ }
+ mIBubbles = new IBubblesImpl(BubbleController.this);
+ return mIBubbles;
+ }
+
@Override
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey);
@@ -1783,15 +2199,14 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void showOrHideAppBubble(Intent intent) {
- mMainExecutor.execute(() -> {
- BubbleController.this.showOrHideAppBubble(intent);
- });
+ public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.showOrHideAppBubble(intent, user, icon));
}
@Override
public boolean isAppBubbleTaskId(int taskId) {
- return mCachedState.mAppBubbleTaskId == taskId;
+ return mCachedState.mAppBubbleTaskIds.values().contains(taskId);
}
@Override
@@ -1918,13 +2333,6 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void setBubbleBarEnabled(boolean enabled) {
- mMainExecutor.execute(() -> {
- BubbleController.this.setBubbleBarEnabled(enabled);
- });
- }
-
- @Override
public void onNotificationPanelExpandedChanged(boolean expanded) {
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
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 6230d22ebe12..cc8f50e09fcb 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
@@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -38,8 +37,11 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.common.bubbles.RemovedBubble;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -113,6 +115,61 @@ public class BubbleData {
void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
removedBubbles.add(new Pair<>(bubbleToRemove, reason));
}
+
+ /**
+ * Converts the update to a {@link BubbleBarUpdate} which contains updates relevant
+ * to the bubble bar. Only used when {@link BubbleController#isShowingAsBubbleBar()} is
+ * true.
+ */
+ BubbleBarUpdate toBubbleBarUpdate() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+
+ bubbleBarUpdate.expandedChanged = expandedChanged;
+ bubbleBarUpdate.expanded = expanded;
+ if (selectionChanged) {
+ bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
+ ? selectedBubble.getKey()
+ : null;
+ }
+ bubbleBarUpdate.addedBubble = addedBubble != null
+ ? addedBubble.asBubbleBarBubble()
+ : null;
+ // TODO(b/269670235): We need to handle updates better, I think for the bubble bar only
+ // certain updates need to be sent instead of any updatedBubble.
+ bubbleBarUpdate.updatedBubble = updatedBubble != null
+ ? updatedBubble.asBubbleBarBubble()
+ : null;
+ bubbleBarUpdate.suppressedBubbleKey = suppressedBubble != null
+ ? suppressedBubble.getKey()
+ : null;
+ bubbleBarUpdate.unsupressedBubbleKey = unsuppressedBubble != null
+ ? unsuppressedBubble.getKey()
+ : null;
+ for (int i = 0; i < removedBubbles.size(); i++) {
+ Pair<Bubble, Integer> pair = removedBubbles.get(i);
+ bubbleBarUpdate.removedBubbles.add(
+ new RemovedBubble(pair.first.getKey(), pair.second));
+ }
+ if (orderChanged) {
+ // Include the new order
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
+ }
+ }
+ return bubbleBarUpdate;
+ }
+
+ /**
+ * Gets the current state of active bubbles and populates the update with that. Only
+ * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
+ */
+ BubbleBarUpdate getInitialState() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
+ }
+ return bubbleBarUpdate;
+ }
}
/**
@@ -190,6 +247,13 @@ public class BubbleData {
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
+ /**
+ * Returns a bubble bar update populated with the current list of active bubbles.
+ */
+ public BubbleBarUpdate getInitialStateForBubbleBar() {
+ return mStateChange.getInitialState();
+ }
+
public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
mBubbleMetadataFlagListener = listener;
}
@@ -270,6 +334,35 @@ public class BubbleData {
dispatchPendingChanges();
}
+ /**
+ * Sets the selected bubble and expands it, but doesn't dispatch changes
+ * to {@link BubbleData.Listener}. This is used for updates coming from launcher whose views
+ * will already be updated so we don't need to notify them again, but BubbleData should be
+ * updated to have the correct state.
+ */
+ public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
+ }
+ mExpanded = true;
+ if (Objects.equals(bubble, mSelectedBubble)) {
+ return;
+ }
+ boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
+ if (bubble != null
+ && !mBubbles.contains(bubble)
+ && !mOverflowBubbles.contains(bubble)
+ && !isOverflow) {
+ Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ + " (" + bubble + ") bubbles=" + mBubbles);
+ return;
+ }
+ if (bubble != null && !isOverflow) {
+ ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
+ }
+ mSelectedBubble = bubble;
+ }
+
public void setSelectedBubble(BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
@@ -283,7 +376,7 @@ public class BubbleData {
}
boolean isShowingOverflow() {
- return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
+ return mShowingOverflow && isExpanded();
}
/**
@@ -686,7 +779,7 @@ public class BubbleData {
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
|| reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
- || KEY_APP_BUBBLE.equals(bubble.getKey())) {
+ || bubble.isAppBubble()) {
return;
}
if (DEBUG_BUBBLE_DATA) {
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 ecddbda0fff4..6c482c831152 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,10 +67,10 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTaskController;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
@@ -229,6 +229,7 @@ public class BubbleExpandedView extends LinearLayout {
options.setLaunchedFromBubble(true);
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
@@ -236,12 +237,17 @@ public class BubbleExpandedView extends LinearLayout {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
if (mBubble.isAppBubble()) {
- PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ PendingIntent pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
mBubble.getAppBubbleIntent()
.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
- PendingIntent.FLAG_IMMUTABLE,
- null);
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ /* options= */ null);
mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
@@ -281,9 +287,9 @@ public class BubbleExpandedView extends LinearLayout {
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
- if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) {
+ if (mBubble != null && mBubble.isAppBubble()) {
// Let the controller know sooner what the taskId is.
- mController.setAppBubbleTaskId(mTaskId);
+ mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
}
// With the task org, the taskAppeared callback will only happen once the task has
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
deleted file mode 100644
index 4ded3ea951e5..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating normalized bubble icons.
- * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
- * so there is no need to manage a pool across multiple threads.
- */
-@VisibleForTesting
-public class BubbleIconFactory extends BaseIconFactory {
-
- public BubbleIconFactory(Context context) {
- super(context, context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
- }
-
- /**
- * Returns the drawable that the developer has provided to display in the bubble.
- */
- Drawable getBubbleDrawable(@NonNull final Context context,
- @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
- if (shortcutInfo != null) {
- LauncherApps launcherApps =
- (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- int density = context.getResources().getConfiguration().densityDpi;
- return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
- } else {
- if (ic != null) {
- if (ic.getType() == Icon.TYPE_URI
- || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
- context.grantUriPermission(context.getPackageName(),
- ic.getUri(),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return ic.loadDrawable(context);
- }
- return null;
- }
- }
-
- /**
- * Creates the bitmap for the provided drawable and returns the scale used for
- * drawing the actual drawable.
- */
- public Bitmap createIconBitmap(@NonNull Drawable icon, float[] outScale) {
- if (outScale == null) {
- outScale = new float[1];
- }
- icon = normalizeAndWrapToAdaptiveIcon(icon,
- true /* shrinkNonAdaptiveIcons */,
- null /* outscale */,
- outScale);
- return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index eb7929b8ca54..c49451df31b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -28,7 +28,9 @@ import android.util.PathParser
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.FrameLayout
+import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
class BubbleOverflow(
private val context: Context,
@@ -92,7 +94,12 @@ class BubbleOverflow(
val shapeColor = res.getColor(android.R.color.system_accent1_1000)
overflowBtn?.iconDrawable?.setTint(shapeColor)
- val iconFactory = BubbleIconFactory(context)
+ val iconFactory = BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width))
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
@@ -136,6 +143,10 @@ class BubbleOverflow(
return expandedView
}
+ override fun getBubbleBarExpandedView(): BubbleBarExpandedView? {
+ return null
+ }
+
override fun getDotColor(): Int {
return dotColor
}
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 07c58527a815..d101b0c4d7e8 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
@@ -18,9 +18,6 @@ package com.android.wm.shell.bubbles;
import static android.view.View.LAYOUT_DIRECTION_RTL;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -39,8 +36,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
-import java.lang.annotation.Retention;
-
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
* placement and positioning calculations to refer to.
@@ -50,15 +45,6 @@ public class BubblePositioner {
? "BubblePositioner"
: BubbleDebugConfig.TAG_BUBBLES;
- @Retention(SOURCE)
- @IntDef({TASKBAR_POSITION_NONE, TASKBAR_POSITION_RIGHT, TASKBAR_POSITION_LEFT,
- TASKBAR_POSITION_BOTTOM})
- @interface TaskbarPosition {}
- public static final int TASKBAR_POSITION_NONE = -1;
- public static final int TASKBAR_POSITION_RIGHT = 0;
- public static final int TASKBAR_POSITION_LEFT = 1;
- public static final int TASKBAR_POSITION_BOTTOM = 2;
-
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
@@ -67,12 +53,16 @@ public class BubblePositioner {
public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
/** The max percent of screen width to use for the flyout on phone. */
public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
- /** The percent of screen width that should be used for the expanded view on a large screen. **/
+ /** The percent of screen width for the expanded view on a large screen. **/
private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
- /** The percent of screen width that should be used for the expanded view on a large screen. **/
+ /** The percent of screen width for the expanded view on a large screen. **/
private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
- /** The percent of screen width that should be used for the expanded view on a small tablet. **/
+ /** The percent of screen width for the expanded view on a small tablet. **/
private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
+ /** The percent of screen width for the expanded view when shown in the bubble bar. **/
+ private static final float EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT = 0.7f;
+ /** The percent of screen width for the expanded view when shown in the bubble bar. **/
+ private static final float EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT = 0.4f;
private Context mContext;
private WindowManager mWindowManager;
@@ -108,14 +98,14 @@ public class BubblePositioner {
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
- private PointF mPinLocation;
private PointF mRestingStackPosition;
private int[] mPaddings = new int[4];
- private boolean mShowingInTaskbar;
- private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
- private int mTaskbarIconSize;
- private int mTaskbarSize;
+ private boolean mShowingInBubbleBar;
+ private boolean mBubblesOnHome;
+ private int mBubbleBarSize;
+ private int mBubbleBarHomeAdjustment;
+ private final PointF mBubbleBarPosition = new PointF();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -153,27 +143,12 @@ public class BubblePositioner {
+ " insets: " + insets
+ " isLargeScreen: " + mIsLargeScreen
+ " isSmallTablet: " + mIsSmallTablet
- + " bounds: " + bounds
- + " showingInTaskbar: " + mShowingInTaskbar);
+ + " showingInBubbleBar: " + mShowingInBubbleBar
+ + " bounds: " + bounds);
}
updateInternal(mRotation, insets, bounds);
}
- /**
- * Updates position information to account for taskbar state.
- *
- * @param taskbarPosition which position the taskbar is displayed in.
- * @param showingInTaskbar whether the taskbar is being shown.
- */
- public void updateForTaskbar(int iconSize,
- @TaskbarPosition int taskbarPosition, boolean showingInTaskbar, int taskbarSize) {
- mShowingInTaskbar = showingInTaskbar;
- mTaskbarIconSize = iconSize;
- mTaskbarPosition = taskbarPosition;
- mTaskbarSize = taskbarSize;
- update();
- }
-
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
mRotation = rotation;
@@ -191,11 +166,17 @@ public class BubblePositioner {
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
+ mBubbleBarHomeAdjustment = mExpandedViewPadding / 2;
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+ mBubbleBarSize = res.getDimensionPixelSize(R.dimen.bubblebar_size);
- if (mIsSmallTablet) {
+ if (mShowingInBubbleBar) {
+ mExpandedViewLargeScreenWidth = isLandscape()
+ ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT)
+ : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT);
+ } else if (mIsSmallTablet) {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
} else {
@@ -232,10 +213,6 @@ public class BubblePositioner {
R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
-
- if (mShowingInTaskbar) {
- adjustForTaskbar();
- }
}
/**
@@ -260,30 +237,6 @@ public class BubblePositioner {
return mDefaultMaxBubbles;
}
- /**
- * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
- * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
- * and insets to account for the taskbar.
- */
- // TODO(b/171559950): When the insets are reported correctly we can remove this logic
- private void adjustForTaskbar() {
- // When bar is showing on edges... subtract that inset because we appear on top
- if (mShowingInTaskbar && mTaskbarPosition != TASKBAR_POSITION_BOTTOM) {
- WindowInsets metricInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- Insets navBarInsets = metricInsets.getInsetsIgnoringVisibility(
- WindowInsets.Type.navigationBars());
- int newInsetLeft = mInsets.left;
- int newInsetRight = mInsets.right;
- if (mTaskbarPosition == TASKBAR_POSITION_LEFT) {
- mPositionRect.left -= navBarInsets.left;
- newInsetLeft -= navBarInsets.left;
- } else if (mTaskbarPosition == TASKBAR_POSITION_RIGHT) {
- mPositionRect.right += navBarInsets.right;
- newInsetRight -= navBarInsets.right;
- }
- mInsets = Insets.of(newInsetLeft, mInsets.top, newInsetRight, mInsets.bottom);
- }
- }
/**
* @return a rect of available screen space accounting for orientation, system bars and cutouts.
@@ -327,14 +280,12 @@ public class BubblePositioner {
* to the left or right side.
*/
public boolean showBubblesVertically() {
- return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
+ return isLandscape() || mIsLargeScreen;
}
/** Size of the bubble. */
public int getBubbleSize() {
- return (mShowingInTaskbar && mTaskbarIconSize > 0)
- ? mTaskbarIconSize
- : mBubbleSize;
+ return mBubbleSize;
}
/** The amount of padding at the top of the screen that the bubbles avoid when being placed. */
@@ -699,9 +650,6 @@ public class BubblePositioner {
/** The position the bubble stack should rest at when collapsed. */
public PointF getRestingPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
if (mRestingStackPosition == null) {
return getDefaultStartPosition();
}
@@ -713,9 +661,6 @@ public class BubblePositioner {
* is being shown.
*/
public PointF getDefaultStartPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
@@ -730,7 +675,6 @@ public class BubblePositioner {
1 /* default starts with 1 bubble */));
}
-
/**
* Returns the region that the stack position must stay within. This goes slightly off the left
* and right sides of the screen, below the status bar/cutout and above the navigation bar.
@@ -751,52 +695,80 @@ public class BubblePositioner {
}
/**
- * @return whether the bubble stack is pinned to the taskbar.
+ * Navigation bar has an area where system gestures can be started from.
+ *
+ * @return {@link Rect} for system navigation bar gesture zone
+ */
+ public Rect getNavBarGestureZone() {
+ // Gesture zone height from the bottom
+ int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ Rect screen = getScreenRect();
+ return new Rect(
+ screen.left,
+ screen.bottom - gestureZoneHeight,
+ screen.right,
+ screen.bottom);
+ }
+
+ //
+ // Bubble bar specific sizes below.
+ //
+
+ /**
+ * Sets whether bubbles are showing in the bubble bar from launcher.
*/
- public boolean showingInTaskbar() {
- return mShowingInTaskbar;
+ public void setShowingInBubbleBar(boolean showingInBubbleBar) {
+ mShowingInBubbleBar = showingInBubbleBar;
}
/**
- * @return the taskbar position if set.
+ * Sets whether bubbles are showing on launcher home, in which case positions are different.
*/
- public int getTaskbarPosition() {
- return mTaskbarPosition;
+ public void setBubblesOnHome(boolean bubblesOnHome) {
+ mBubblesOnHome = bubblesOnHome;
}
- public int getTaskbarSize() {
- return mTaskbarSize;
+ /**
+ * How wide the expanded view should be when showing from the bubble bar.
+ */
+ public int getExpandedViewWidthForBubbleBar() {
+ return mExpandedViewLargeScreenWidth;
}
/**
- * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
- * bubbles should be pinned or not.
+ * How tall the expanded view should be when showing from the bubble bar.
*/
- public void setUsePinnedLocation(boolean usePinnedLocation) {
- if (usePinnedLocation) {
- mShowingInTaskbar = true;
- mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
- mPositionRect.bottom - mBubbleSize);
- } else {
- mPinLocation = null;
- mShowingInTaskbar = false;
- }
+ public int getExpandedViewHeightForBubbleBar() {
+ return getAvailableRect().height()
+ - mBubbleBarSize
+ - mExpandedViewPadding * 2
+ - getBubbleBarHomeAdjustment();
}
/**
- * Navigation bar has an area where system gestures can be started from.
- *
- * @return {@link Rect} for system navigation bar gesture zone
+ * The amount of padding from the edge of the screen to the expanded view when in bubble bar.
*/
- public Rect getNavBarGestureZone() {
- // Gesture zone height from the bottom
- int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_gesture_height);
- Rect screen = getScreenRect();
- return new Rect(
- screen.left,
- screen.bottom - gestureZoneHeight,
- screen.right,
- screen.bottom);
+ public int getBubbleBarExpandedViewPadding() {
+ return mExpandedViewPadding;
+ }
+
+ /**
+ * Returns the on screen co-ordinates of the bubble bar.
+ */
+ public PointF getBubbleBarPosition() {
+ mBubbleBarPosition.set(getAvailableRect().width() - mBubbleBarSize,
+ getAvailableRect().height() - mBubbleBarSize
+ - mExpandedViewPadding - getBubbleBarHomeAdjustment());
+ return mBubbleBarPosition;
+ }
+
+ /**
+ * When bubbles are shown on launcher home, there's an extra bit of padding that needs to
+ * be applied between the expanded view and the bubble bar. This returns the adjustment value
+ * if bubbles are showing on home.
+ */
+ private int getBubbleBarHomeAdjustment() {
+ return mBubblesOnHome ? mBubbleBarHomeAdjustment : 0;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index abe42eec7061..1b20f67e42ab 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
@@ -33,7 +33,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -575,7 +574,7 @@ public class BubbleStackView extends FrameLayout
if (maybeShowStackEdu()) {
mShowedUserEducationInTouchListenerActive = true;
return true;
- } else if (isStackEduShowing()) {
+ } else if (isStackEduVisible()) {
mStackEduView.hide(false /* fromExpansion */);
}
@@ -651,7 +650,7 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
mStackEduView.hide(false /* fromExpansion */);
}
mStackAnimationController.moveStackFromTouch(
@@ -680,8 +679,6 @@ public class BubbleStackView extends FrameLayout
// Re-show the expanded view if we hid it.
showExpandedViewIfNeeded();
- } else if (mPositioner.showingInTaskbar()) {
- mStackAnimationController.snapStackBack();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
@@ -735,8 +732,7 @@ public class BubbleStackView extends FrameLayout
@Override
public void onMove(float dx, float dy) {
- if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE)
- || isStackEduShowing()) {
+ if (isManageEduVisible() || isStackEduVisible()) {
return;
}
@@ -846,6 +842,8 @@ public class BubbleStackView extends FrameLayout
private DismissView mDismissView;
private ViewGroup mManageMenu;
+ private ViewGroup mManageDontBubbleView;
+ private ViewGroup mManageSettingsView;
private ImageView mManageSettingsIcon;
private TextView mManageSettingsText;
private boolean mShowingManage = false;
@@ -996,7 +994,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mBubbleOverflow.updateResources();
- if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) {
+ if (!isStackEduVisible() && mRelativeStackPositionBeforeRotation != null) {
mStackAnimationController.setStackPosition(
mRelativeStackPositionBeforeRotation);
mRelativeStackPositionBeforeRotation = null;
@@ -1046,9 +1044,9 @@ public class BubbleStackView extends FrameLayout
setOnClickListener(view -> {
if (mShowingManage) {
showManageMenu(false /* show */);
- } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ } else if (isManageEduVisible()) {
mManageEduView.hide();
- } else if (isStackEduShowing()) {
+ } else if (isStackEduVisible()) {
mStackEduView.hide(false /* isExpanding */);
} else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
@@ -1219,7 +1217,11 @@ public class BubbleStackView extends FrameLayout
mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
});
- mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+ mManageDontBubbleView = mManageMenu
+ .findViewById(R.id.bubble_manage_menu_dont_bubble_container);
+
+ mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container);
+ mManageSettingsView.setOnClickListener(
view -> {
showManageMenu(false /* show */);
final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
@@ -1243,10 +1245,19 @@ public class BubbleStackView extends FrameLayout
}
/**
+ * Whether the selected bubble is conversation bubble
+ */
+ private boolean isConversationBubble() {
+ BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
+ return bubble instanceof Bubble && ((Bubble) bubble).isConversation();
+ }
+
+ /**
* Whether the educational view should show for the expanded view "manage" menu.
*/
private boolean shouldShowManageEdu() {
- if (ActivityManager.isRunningInTestHarness()) {
+ if (!isConversationBubble()) {
+ // We only show user education for conversation bubbles right now
return false;
}
final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
@@ -1269,11 +1280,17 @@ public class BubbleStackView extends FrameLayout
mManageEduView.show(mExpandedBubble.getExpandedView());
}
+ @VisibleForTesting
+ public boolean isManageEduVisible() {
+ return mManageEduView != null && mManageEduView.getVisibility() == VISIBLE;
+ }
+
/**
* Whether education view should show for the collapsed stack.
*/
private boolean shouldShowStackEdu() {
- if (ActivityManager.isRunningInTestHarness()) {
+ if (!isConversationBubble()) {
+ // We only show user education for conversation bubbles right now
return false;
}
final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
@@ -1306,13 +1323,14 @@ public class BubbleStackView extends FrameLayout
return mStackEduView.show(mPositioner.getDefaultStartPosition());
}
- private boolean isStackEduShowing() {
+ @VisibleForTesting
+ public boolean isStackEduVisible() {
return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE;
}
// Recreates & shows the education views. Call when a theme/config change happens.
private void updateUserEdu() {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
removeView(mStackEduView);
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
@@ -1321,7 +1339,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
mStackEduView.show(mPositioner.getDefaultStartPosition());
}
- if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ if (isManageEduVisible()) {
removeView(mManageEduView);
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
@@ -1361,16 +1379,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
}
- void updateOverflowButtonDot() {
- for (Bubble b : mBubbleData.getOverflowBubbles()) {
- if (b.showDot()) {
- mBubbleOverflow.setShowDot(true);
- return;
- }
- }
- mBubbleOverflow.setShowDot(false);
- }
-
/**
* Handle theme changes.
*/
@@ -1435,7 +1443,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mDismissView.updateResources();
mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
- if (!isStackEduShowing()) {
+ if (!isStackEduVisible()) {
mStackAnimationController.setStackPosition(
new RelativeStackPosition(
mPositioner.getRestingPosition(),
@@ -2019,7 +2027,7 @@ public class BubbleStackView extends FrameLayout
if (mIsExpanded) {
if (mShowingManage) {
showManageMenu(false);
- } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ } else if (isManageEduVisible()) {
mManageEduView.hide();
} else {
mBubbleData.setExpanded(false);
@@ -2164,7 +2172,7 @@ public class BubbleStackView extends FrameLayout
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
mStackEduView.hide(true /* fromExpansion */);
}
beforeExpandedViewAnimation();
@@ -2286,7 +2294,7 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
- if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2683,7 +2691,7 @@ public class BubbleStackView extends FrameLayout
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
- || isStackEduShowing()
+ || isStackEduVisible()
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -2806,7 +2814,7 @@ public class BubbleStackView extends FrameLayout
* them.
*/
public void getTouchableRegion(Rect outRect) {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
// When user education shows then capture all touches
outRect.set(0, 0, getWidth(), getHeight());
return;
@@ -2880,10 +2888,21 @@ public class BubbleStackView extends FrameLayout
// name and icon.
if (show) {
final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
- if (bubble != null) {
+ if (bubble != null && !bubble.isAppBubble()) {
+ // Setup options for non app bubbles
+ mManageDontBubbleView.setVisibility(VISIBLE);
mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
mManageSettingsText.setText(getResources().getString(
R.string.bubbles_app_settings, bubble.getAppName()));
+ mManageSettingsView.setVisibility(VISIBLE);
+ } else {
+ // Setup options for app bubbles
+ // App bubbles have no conversations
+ // so we don't show the option to not bubble conversation
+ mManageDontBubbleView.setVisibility(GONE);
+ // App bubbles are not notification based
+ // so we don't show the option to go to notification settings
+ mManageSettingsView.setVisibility(GONE);
}
}
@@ -2948,6 +2967,24 @@ public class BubbleStackView extends FrameLayout
}
}
+ /**
+ * Checks whether manage menu don't bubble conversation action is available and visible
+ * Used for testing
+ */
+ @VisibleForTesting
+ public boolean isManageMenuDontBubbleVisible() {
+ return mManageDontBubbleView != null && mManageDontBubbleView.getVisibility() == VISIBLE;
+ }
+
+ /**
+ * Checks whether manage menu notification settings action is available and visible
+ * Used for testing
+ */
+ @VisibleForTesting
+ public boolean isManageMenuSettingsVisible() {
+ return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE;
+ }
+
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
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
new file mode 100644
index 000000000000..7a5815994dd0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
+
+/**
+ * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
+ */
+public class BubbleTaskViewHelper {
+
+ private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();
+
+ /**
+ * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
+ * on the task.
+ */
+ public interface Listener {
+
+ /** Called when the task is first created. */
+ void onTaskCreated();
+
+ /** Called when the visibility of the task changes. */
+ void onContentVisibilityChanged(boolean visible);
+
+ /** Called when back is pressed on the task root. */
+ void onBackPressed();
+ }
+
+ private final Context mContext;
+ private final BubbleController mController;
+ private final @ShellMainThread ShellExecutor mMainExecutor;
+ private final BubbleTaskViewHelper.Listener mListener;
+ private final View mParentView;
+
+ @Nullable
+ private Bubble mBubble;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ private TaskViewTaskController mTaskViewTaskController;
+ @Nullable
+ private TaskView mTaskView;
+ private int mTaskId = INVALID_TASK_ID;
+
+ private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
+
+ @Override
+ public void onInitialized() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
+ + " initialized=" + mInitialized
+ + " bubble=" + getBubbleKey());
+ }
+
+ if (mDestroyed || mInitialized) {
+ return;
+ }
+
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ Rect launchBounds = new Rect();
+ mTaskView.getBoundsOnScreen(launchBounds);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ mParentView.post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: calling startActivity, bubble="
+ + getBubbleKey());
+ }
+ try {
+ options.setTaskAlwaysOnTop(true);
+ options.setLaunchedFromBubble(true);
+
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ if (mBubble.isAppBubble()) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ mBubble.getAppBubbleIntent(),
+ PendingIntent.FLAG_MUTABLE,
+ null);
+ mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ } else if (mBubble.hasMetadataShortcutId()) {
+ options.setApplyActivityFlagsForBubbles(true);
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, launchBounds);
+ } else {
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options,
+ launchBounds);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ }
+ mInitialized = true;
+ });
+ }
+
+ @Override
+ public void onReleased() {
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName name) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ mListener.onTaskCreated();
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ mListener.onContentVisibilityChanged(visible);
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ if (mBubble != null) {
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mController.isStackExpanded()) {
+ mListener.onBackPressed();
+ }
+ }
+ };
+
+ public BubbleTaskViewHelper(Context context,
+ BubbleController controller,
+ BubbleTaskViewHelper.Listener listener,
+ View parent) {
+ mContext = context;
+ mController = controller;
+ mMainExecutor = mController.getMainExecutor();
+ mListener = listener;
+ mParentView = parent;
+ mTaskViewTaskController = new TaskViewTaskController(mContext,
+ mController.getTaskOrganizer(),
+ mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ }
+
+ /**
+ * Sets the bubble or updates the bubble used to populate the view.
+ *
+ * @return true if the bubble is new, false if it was an update to the same bubble.
+ */
+ public boolean update(Bubble bubble) {
+ boolean isNew = mBubble == null || didBackingContentChange(bubble);
+ mBubble = bubble;
+ if (isNew) {
+ mPendingIntent = mBubble.getBubbleIntent();
+ return true;
+ }
+ return false;
+ }
+
+ /** Cleans up anything related to the task and {@code TaskView}. */
+ public void cleanUpTaskView() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
+ }
+ if (mTaskId != INVALID_TASK_ID) {
+ try {
+ ActivityTaskManager.getService().removeTask(mTaskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, e.getMessage());
+ }
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ mTaskView = null;
+ }
+ }
+
+ /** Returns the bubble key associated with this view. */
+ @Nullable
+ public String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : null;
+ }
+
+ /** Returns the TaskView associated with this view. */
+ @Nullable
+ public TaskView getTaskView() {
+ return mTaskView;
+ }
+
+ /**
+ * Returns the task id associated with the task in this view. If the task doesn't exist then
+ * {@link ActivityTaskManager#INVALID_TASK_ID}.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /** Returns whether the bubble set on the helper is valid to populate the task view. */
+ public boolean isValidBubble() {
+ return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
+ }
+
+ // TODO (b/274980695): Is this still relevant?
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubble's
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
+ private boolean didBackingContentChange(Bubble newBubble) {
+ boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
+ boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
+ return prevWasIntentBased != newIsIntentBased;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index f437553337ef..8ab9841ff0c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -22,6 +22,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -38,12 +39,13 @@ import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -70,8 +72,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
private WeakReference<Context> mContext;
private WeakReference<BubbleController> mController;
private WeakReference<BubbleStackView> mStackView;
+ private WeakReference<BubbleBarLayerView> mLayerView;
private BubbleIconFactory mIconFactory;
- private BubbleBadgeIconFactory mBadgeIconFactory;
private boolean mSkipInflation;
private Callback mCallback;
private Executor mMainExecutor;
@@ -83,9 +85,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
BubbleViewInfoTask(Bubble b,
Context context,
BubbleController controller,
- BubbleStackView stackView,
+ @Nullable BubbleStackView stackView,
+ @Nullable BubbleBarLayerView layerView,
BubbleIconFactory factory,
- BubbleBadgeIconFactory badgeFactory,
boolean skipInflation,
Callback c,
Executor mainExecutor) {
@@ -93,8 +95,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
mContext = new WeakReference<>(context);
mController = new WeakReference<>(controller);
mStackView = new WeakReference<>(stackView);
+ mLayerView = new WeakReference<>(layerView);
mIconFactory = factory;
- mBadgeIconFactory = badgeFactory;
mSkipInflation = skipInflation;
mCallback = c;
mMainExecutor = mainExecutor;
@@ -102,8 +104,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
- return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
- mIconFactory, mBadgeIconFactory, mBubble, mSkipInflation);
+ if (mController.get().isShowingAsBubbleBar()) {
+ return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
+ mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
+ } else {
+ return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
+ mIconFactory, mBubble, mSkipInflation);
+ }
}
@Override
@@ -124,22 +131,75 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
*/
@VisibleForTesting
public static class BubbleViewInfo {
- BadgedImageView imageView;
- BubbleExpandedView expandedView;
+ // TODO(b/273312602): for foldables it might make sense to populate all of the views
+
+ // Always populated
ShortcutInfo shortcutInfo;
String appName;
- Bitmap bubbleBitmap;
- Bitmap badgeBitmap;
- Bitmap mRawBadgeBitmap;
+ Bitmap rawBadgeBitmap;
+
+ // Only populated when showing in taskbar
+ BubbleBarExpandedView bubbleBarExpandedView;
+
+ // These are only populated when not showing in taskbar
+ BadgedImageView imageView;
+ BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
+ Bitmap bubbleBitmap;
+ Bitmap badgeBitmap;
+
+ @Nullable
+ public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
+ BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b,
+ boolean skipInflation) {
+ BubbleViewInfo info = new BubbleViewInfo();
+
+ if (!skipInflation && !b.isInflated()) {
+ LayoutInflater inflater = LayoutInflater.from(c);
+ info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
+ R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
+ info.bubbleBarExpandedView.initialize(controller);
+ }
+
+ if (b.getShortcutInfo() != null) {
+ info.shortcutInfo = b.getShortcutInfo();
+ }
+
+ // App name & app icon
+ PackageManager pm = BubbleController.getPackageManagerForUser(c,
+ b.getUser().getIdentifier());
+ ApplicationInfo appInfo;
+ Drawable badgedIcon;
+ Drawable appIcon;
+ try {
+ appInfo = pm.getApplicationInfo(
+ b.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (appInfo != null) {
+ info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+ }
+ appIcon = pm.getApplicationIcon(b.getPackageName());
+ badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
+ } catch (PackageManager.NameNotFoundException exception) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find package: " + b.getPackageName());
+ return null;
+ }
+
+ info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
+
+ return info;
+ }
@VisibleForTesting
@Nullable
public static BubbleViewInfo populate(Context c, BubbleController controller,
- BubbleStackView stackView, BubbleIconFactory iconFactory,
- BubbleBadgeIconFactory badgeIconFactory, Bubble b,
+ BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
@@ -191,16 +251,16 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
bubbleDrawable = appIcon;
}
- BitmapInfo badgeBitmapInfo = badgeIconFactory.getBadgeBitmap(badgedIcon,
+ BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
b.isImportantConversation());
info.badgeBitmap = badgeBitmapInfo.icon;
// Raw badge bitmap never includes the important conversation ring
- info.mRawBadgeBitmap = b.isImportantConversation()
- ? badgeIconFactory.getBadgeBitmap(badgedIcon, false).icon
+ info.rawBadgeBitmap = b.isImportantConversation()
+ ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
: badgeBitmapInfo.icon;
float[] bubbleBitmapScale = new float[1];
- info.bubbleBitmap = iconFactory.createIconBitmap(bubbleDrawable, bubbleBitmapScale);
+ info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
// Dot color & placement
Path iconPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index 3f6d41bb2b68..6bdc3b9f5aec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -22,18 +22,40 @@ import android.view.View;
import androidx.annotation.Nullable;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+
/**
* Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
*/
public interface BubbleViewProvider {
- @Nullable BubbleExpandedView getExpandedView();
+
+ /**
+ * Returns the icon view used for a bubble (the click target when collapsed). This is populated
+ * when bubbles are floating, i.e. when {@link BubbleController#isShowingAsBubbleBar()} is
+ * false.
+ */
+ @Nullable
+ View getIconView();
+
+ /**
+ * Returns the expanded view used for a bubble. This is populated when bubbles are floating,
+ * i.e. when {@link BubbleController#isShowingAsBubbleBar()} is false.
+ */
+ @Nullable
+ BubbleExpandedView getExpandedView();
+
+ /**
+ * Returns the expanded view used for a bubble being show in the bubble bar. This is populated
+ * when {@link BubbleController#isShowingAsBubbleBar()} is true.
+ */
+ @Nullable
+ BubbleBarExpandedView getBubbleBarExpandedView();
/**
* Sets whether the contents of the bubble's TaskView should be visible.
*/
void setTaskViewVisibility(boolean visible);
- @Nullable View getIconView();
String getKey();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 1753cda895fe..259f69296ac7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -26,6 +26,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Icon;
import android.hardware.HardwareBuffer;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -38,6 +39,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -80,6 +82,11 @@ public interface Bubbles {
int DISMISS_RELOAD_FROM_DISK = 15;
int DISMISS_USER_REMOVED = 16;
+ /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
+ default IBubbles createExternalInterface() {
+ return null;
+ }
+
/**
* @return {@code true} if there is a bubble associated with the provided key and if its
* notification is hidden from the shade or there is a group summary associated with the
@@ -129,12 +136,15 @@ public interface Bubbles {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
- void showOrHideAppBubble(Intent intent);
+ void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
/** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
boolean isAppBubbleTaskId(int taskId);
@@ -274,9 +284,15 @@ public interface Bubbles {
void onUserRemoved(int removedUserId);
/**
- * Sets whether bubble bar should be enabled or not.
+ * A listener to be notified of bubble state changes, used by launcher to render bubbles in
+ * its process.
*/
- void setBubbleBarEnabled(boolean enabled);
+ interface BubbleStateListener {
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(BubbleBarUpdate update);
+ }
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
new file mode 100644
index 000000000000..862e818a998b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Intent;
+import com.android.wm.shell.bubbles.IBubblesListener;
+
+/**
+ * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
+ * showing in the bubble bar.
+ */
+interface IBubbles {
+
+ oneway void registerBubbleListener(in IBubblesListener listener) = 1;
+
+ oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
+
+ oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+
+ oneway void removeBubble(in String key, in int reason) = 4;
+
+ oneway void collapseBubbles() = 5;
+
+ oneway void onTaskbarStateChanged(in int newState) = 6;
+
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
new file mode 100644
index 000000000000..e48f8d5f1c84
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+import android.os.Bundle;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
+ */
+oneway interface IBubblesListener {
+
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(in Bundle update);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0ee0ea60a1bc..5533842f2d89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -417,23 +417,9 @@ public class StackAnimationController extends
}
/**
- * Snaps the stack back to the previous resting position.
- */
- public void snapStackBack() {
- if (mLayout == null) {
- return;
- }
- PointF p = getStackPositionAlongNearestHorizontalEdge();
- springStackAfterFling(p.x, p.y);
- }
-
- /**
* Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
*/
public PointF getStackPositionAlongNearestHorizontalEdge() {
- if (mPositioner.showingInTaskbar()) {
- return mPositioner.getRestingPosition();
- }
final PointF stackPos = getStackPosition();
final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
final RectF bounds = mPositioner.getAllowableStackPositionRegion(getBubbleCount());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
new file mode 100644
index 000000000000..23f65f943aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import static android.view.View.VISIBLE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.Log;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleViewProvider;
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
+
+/**
+ * Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
+ */
+public class BubbleBarAnimationHelper {
+
+ private static final String TAG = BubbleBarAnimationHelper.class.getSimpleName();
+
+ private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
+ private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
+ private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+
+ /** Spring config for the expanded view scale-in animation. */
+ private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
+ new PhysicsAnimator.SpringConfig(300f, 0.9f);
+
+ /** Spring config for the expanded view scale-out animation. */
+ private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
+ new PhysicsAnimator.SpringConfig(900f, 1f);
+
+ /** Matrix used to scale the expanded view container with a given pivot point. */
+ private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
+
+ /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
+ private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
+ private final Context mContext;
+ private final BubbleBarLayerView mLayerView;
+ private final BubblePositioner mPositioner;
+
+ private BubbleViewProvider mExpandedBubble;
+ private boolean mIsExpanded = false;
+
+ public BubbleBarAnimationHelper(Context context,
+ BubbleBarLayerView bubbleBarLayerView,
+ BubblePositioner positioner) {
+ mContext = context;
+ mLayerView = bubbleBarLayerView;
+ mPositioner = positioner;
+
+ mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+ mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ // We need to be Z ordered on top in order for alpha animations to work.
+ mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true);
+ mExpandedBubble.getBubbleBarExpandedView().setAnimating(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ // The surface needs to be Z ordered on top for alpha values to work on the
+ // TaskView, and if we're temporarily hidden, we are still on the screen
+ // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
+ // = 0f remains in effect.
+ if (mIsExpanded) {
+ mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false);
+ }
+
+ mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded);
+ mExpandedBubble.getBubbleBarExpandedView().setAnimating(false);
+ }
+ }
+ });
+ mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
+ if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ float alpha = (float) valueAnimator.getAnimatedValue();
+ mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha);
+ mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha);
+ }
+ });
+ }
+
+ /**
+ * Animates the provided bubble's expanded view to the expanded state.
+ */
+ public void animateExpansion(BubbleViewProvider expandedBubble) {
+ mExpandedBubble = expandedBubble;
+ if (mExpandedBubble == null) {
+ return;
+ }
+ BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView();
+ if (bev == null) {
+ return;
+ }
+ mIsExpanded = true;
+
+ mExpandedViewContainerMatrix.setScaleX(0f);
+ mExpandedViewContainerMatrix.setScaleY(0f);
+
+ updateExpandedView();
+ bev.setAnimating(true);
+ bev.setContentVisibility(false);
+ bev.setAlpha(0f);
+ bev.setTaskViewAlpha(0f);
+ bev.setVisibility(VISIBLE);
+
+ // Set the pivot point for the scale, so the view animates out from the bubble bar.
+ PointF bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ mExpandedViewContainerMatrix.setScale(
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ bubbleBarPosition.x,
+ bubbleBarPosition.y);
+
+ bev.setAnimationMatrix(mExpandedViewContainerMatrix);
+
+ mExpandedViewAlphaAnimator.start();
+
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .addUpdateListener((target, values) -> {
+ mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
+ mExpandedViewContainerMatrix);
+ })
+ .withEndActions(() -> {
+ bev.setAnimationMatrix(null);
+ updateExpandedView();
+ bev.setSurfaceZOrderedOnTop(false);
+ })
+ .start();
+ }
+
+ /**
+ * Collapses the currently expanded bubble.
+ *
+ * @param endRunnable a runnable to run at the end of the animation.
+ */
+ public void animateCollapse(Runnable endRunnable) {
+ mIsExpanded = false;
+ if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+ Log.w(TAG, "Trying to animate collapse without a bubble");
+ return;
+ }
+
+ mExpandedViewContainerMatrix.setScaleX(1f);
+ mExpandedViewContainerMatrix.setScaleY(1f);
+
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
+ .addUpdateListener((target, values) -> {
+ if (mExpandedBubble != null
+ && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
+ mExpandedViewContainerMatrix);
+ }
+ })
+ .withEndActions(() -> {
+ if (mExpandedBubble != null
+ && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null);
+ }
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ })
+ .start();
+ mExpandedViewAlphaAnimator.reverse();
+ }
+
+ private void updateExpandedView() {
+ if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+ Log.w(TAG, "Trying to update the expanded view without a bubble");
+ return;
+ }
+ BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
+
+ final int padding = mPositioner.getBubbleBarExpandedViewPadding();
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar();
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
+ lp.width = width;
+ lp.height = height;
+ bbev.setLayoutParams(lp);
+ if (mLayerView.isOnLeft()) {
+ bbev.setX(mPositioner.getInsets().left + padding);
+ } else {
+ bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
+ }
+ bbev.setY(mPositioner.getInsets().top + padding);
+ bbev.updateLocation();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
new file mode 100644
index 000000000000..b8f049becb6f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.bar;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.taskview.TaskView;
+
+/**
+ * Expanded view of a bubble when it's part of the bubble bar.
+ *
+ * {@link BubbleController#isShowingAsBubbleBar()}
+ */
+public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
+
+ private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
+ private static final int INVALID_TASK_ID = -1;
+
+ private BubbleController mController;
+ private BubbleTaskViewHelper mBubbleTaskViewHelper;
+
+ private HandleView mMenuView;
+ private TaskView mTaskView;
+
+ private int mMenuHeight;
+ private int mBackgroundColor;
+ private float mCornerRadius = 0f;
+
+ /**
+ * Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
+ * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
+ * value until the animation ends.
+ */
+ private boolean mIsContentVisible = false;
+ private boolean mIsAnimating;
+
+ public BubbleBarExpandedView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleBarExpandedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleBarExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleBarExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ Context context = getContext();
+ setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
+ mMenuHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_expanded_view_menu_size);
+ mMenuView = new HandleView(context);
+ addView(mMenuView);
+
+ applyThemeAttrs();
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ }
+ });
+ }
+
+ /** Set the BubbleController on the view, must be called before doing anything else. */
+ public void initialize(BubbleController controller) {
+ mController = controller;
+ mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+ /* listener= */ this,
+ /* viewParent= */ this);
+ mTaskView = mBubbleTaskViewHelper.getTaskView();
+ addView(mTaskView);
+ mTaskView.setEnableSurfaceClipping(true);
+ mTaskView.setCornerRadius(mCornerRadius);
+ }
+
+ // TODO (b/275087636): call this when theme/config changes
+ void applyThemeAttrs() {
+ boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources());
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ android.R.attr.dialogCornerRadius,
+ android.R.attr.colorBackgroundFloating});
+ mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
+ mCornerRadius = mCornerRadius / 2f;
+ mBackgroundColor = ta.getColor(1, Color.WHITE);
+
+ ta.recycle();
+
+ mMenuView.setCornerRadius(mCornerRadius);
+ mMenuHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_expanded_view_menu_size);
+
+ if (mTaskView != null) {
+ mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setElevation(150);
+ updateMenuColor();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
+ int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
+ measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
+ MeasureSpec.getMode(heightMeasureSpec)));
+
+ if (mTaskView != null) {
+ int taskViewHeight = height - menuViewHeight;
+ measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+ MeasureSpec.getMode(heightMeasureSpec)));
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // Drag handle above
+ final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
+ mMenuView.layout(l, t, r, dragHandleBottom);
+ if (mTaskView != null) {
+ // Subtract radius so that the menu extends behind the rounded corners of TaskView.
+ mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
+ dragHandleBottom + mTaskView.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public void onTaskCreated() {
+ setContentVisibility(true);
+ updateMenuColor();
+ }
+
+ @Override
+ public void onContentVisibilityChanged(boolean visible) {
+ setContentVisibility(visible);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mController.collapseStack();
+ }
+
+ /** Cleans up task view, should be called when the bubble is no longer active. */
+ public void cleanUpExpandedState() {
+ if (mBubbleTaskViewHelper != null) {
+ if (mTaskView != null) {
+ removeView(mTaskView);
+ }
+ mBubbleTaskViewHelper.cleanUpTaskView();
+ }
+ }
+
+ /** Updates the bubble shown in this task view. */
+ public void update(Bubble bubble) {
+ mBubbleTaskViewHelper.update(bubble);
+ }
+
+ /** The task id of the activity shown in the task view, if it exists. */
+ public int getTaskId() {
+ return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
+ }
+
+ /**
+ * Call when the location or size of the view has changed to update TaskView.
+ */
+ public void updateLocation() {
+ if (mTaskView == null) return;
+ mTaskView.onLocationChanged();
+ }
+
+ /** Sets the alpha of the task view. */
+ public void setContentVisibility(boolean visible) {
+ mIsContentVisible = visible;
+
+ if (mTaskView == null) return;
+
+ if (!mIsAnimating) {
+ mTaskView.setAlpha(visible ? 1f : 0f);
+ }
+ }
+
+ /** Updates the menu bar to be the status bar color specified by the app. */
+ private void updateMenuColor() {
+ if (mTaskView == null) return;
+ ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
+ final int taskBgColor = info.taskDescription.getStatusBarColor();
+ final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
+ if (color != -1) {
+ mMenuView.setBackgroundColor(color);
+ } else {
+ mMenuView.setBackgroundColor(mBackgroundColor);
+ }
+ }
+
+ /**
+ * Sets the alpha of both this view and the task view.
+ */
+ public void setTaskViewAlpha(float alpha) {
+ if (mTaskView != null) {
+ mTaskView.setAlpha(alpha);
+ }
+ setAlpha(alpha);
+ }
+
+ /**
+ * Sets whether the surface displaying app content should sit on top. This is useful for
+ * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
+ * being dragged out, the manage menu) this is set to false, otherwise it should be true.
+ */
+ public void setSurfaceZOrderedOnTop(boolean onTop) {
+ if (mTaskView == null) {
+ return;
+ }
+ mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
+ }
+
+ /**
+ * Sets whether the view is animating, in this case we won't change the content visibility
+ * until the animation is done.
+ */
+ public void setAnimating(boolean animating) {
+ mIsAnimating = animating;
+ // If we're done animating, apply the correct visibility.
+ if (!animating) {
+ setContentVisibility(mIsContentVisible);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
new file mode 100644
index 000000000000..b1a725b6e5c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.bar;
+
+import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.ColorDrawable;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleViewProvider;
+
+/**
+ * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
+ * manager to display bubbles. However, it is only used when bubbles are being displayed in
+ * launcher in the bubble bar. This view does not show a stack of bubbles that can be moved around
+ * on screen and instead shows & animates the expanded bubble for the bubble bar.
+ */
+public class BubbleBarLayerView extends FrameLayout
+ implements ViewTreeObserver.OnComputeInternalInsetsListener {
+
+ private static final String TAG = BubbleBarLayerView.class.getSimpleName();
+
+ private static final float SCRIM_ALPHA = 0.2f;
+
+ private final BubbleController mBubbleController;
+ private final BubblePositioner mPositioner;
+ private final BubbleBarAnimationHelper mAnimationHelper;
+ private final View mScrimView;
+
+ @Nullable
+ private BubbleViewProvider mExpandedBubble;
+ private BubbleBarExpandedView mExpandedView;
+
+ // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
+ /** Whether the expanded view is displaying on the left of the screen or not. */
+ private boolean mOnLeft = false;
+
+ /** Whether a bubble is expanded. */
+ private boolean mIsExpanded = false;
+
+ private final Region mTouchableRegion = new Region();
+ private final Rect mTempRect = new Rect();
+
+ public BubbleBarLayerView(Context context, BubbleController controller) {
+ super(context);
+ mBubbleController = controller;
+ mPositioner = mBubbleController.getPositioner();
+
+ mAnimationHelper = new BubbleBarAnimationHelper(context,
+ this, mPositioner);
+
+ mScrimView = new View(getContext());
+ mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mScrimView.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
+ addView(mScrimView);
+ mScrimView.setAlpha(0f);
+ mScrimView.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
+
+ setOnClickListener(view -> {
+ mBubbleController.collapseStack();
+ });
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPositioner.update();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+
+ if (mExpandedView != null) {
+ removeView(mExpandedView);
+ mExpandedView = null;
+ }
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ mTouchableRegion.setEmpty();
+ getTouchableRegion(mTouchableRegion);
+ inoutInfo.touchableRegion.set(mTouchableRegion);
+ }
+
+ /** Updates the sizes of any displaying expanded view. */
+ public void onDisplaySizeChanged() {
+ if (mIsExpanded && mExpandedView != null) {
+ updateExpandedView();
+ }
+ }
+
+ /** Whether the stack of bubbles is expanded or not. */
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+
+ // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+ /** Whether the expanded view is positioned on the left or right side of the screen. */
+ public boolean isOnLeft() {
+ return mOnLeft;
+ }
+
+ /** Shows the expanded view of the provided bubble. */
+ public void showExpandedView(BubbleViewProvider b) {
+ BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
+ if (expandedView == null) {
+ return;
+ }
+ if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
+ removeView(mExpandedView);
+ mExpandedView = null;
+ }
+ if (mExpandedView == null) {
+ mExpandedBubble = b;
+ mExpandedView = expandedView;
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar();
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ mExpandedView.setVisibility(GONE);
+ addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+ }
+
+ mIsExpanded = true;
+ mBubbleController.getSysuiProxy().onStackExpandChanged(true);
+ mAnimationHelper.animateExpansion(mExpandedBubble);
+ showScrim(true);
+ }
+
+ /** Collapses any showing expanded view */
+ public void collapse() {
+ mIsExpanded = false;
+ final BubbleBarExpandedView viewToRemove = mExpandedView;
+ mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ mBubbleController.getSysuiProxy().onStackExpandChanged(false);
+ mExpandedView = null;
+ showScrim(false);
+ }
+
+ /** Updates the expanded view size and position. */
+ private void updateExpandedView() {
+ if (mExpandedView == null) return;
+ final int padding = mPositioner.getBubbleBarExpandedViewPadding();
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar();
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams();
+ lp.width = width;
+ lp.height = height;
+ mExpandedView.setLayoutParams(lp);
+ if (mOnLeft) {
+ mExpandedView.setX(mPositioner.getInsets().left + padding);
+ } else {
+ mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
+ }
+ mExpandedView.setY(mPositioner.getInsets().top + padding);
+ mExpandedView.updateLocation();
+ }
+
+ private void showScrim(boolean show) {
+ if (show) {
+ mScrimView.animate()
+ .setInterpolator(ALPHA_IN)
+ .alpha(SCRIM_ALPHA)
+ .start();
+ } else {
+ mScrimView.animate()
+ .alpha(0f)
+ .setInterpolator(ALPHA_OUT)
+ .start();
+ }
+ }
+
+ /**
+ * Fills in the touchable region for expanded view. This is used by window manager to
+ * decide which touch events go to the expanded view.
+ */
+ private void getTouchableRegion(Region outRegion) {
+ mTempRect.setEmpty();
+ if (mIsExpanded) {
+ getBoundsOnScreen(mTempRect);
+ outRegion.op(mTempRect, Region.Op.UNION);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
new file mode 100644
index 000000000000..9ee8a9d98aa1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.widget.LinearLayout;
+
+/**
+ * Handle / menu view to show at the top of a bubble bar expanded view.
+ */
+public class HandleView extends LinearLayout {
+
+ // TODO(b/273307221): implement the manage menu in this view.
+ public HandleView(Context context) {
+ super(context);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER);
+ }
+
+ /**
+ * The menu extends past the top of the TaskView because of the rounded corners. This means
+ * to center content in the menu we must subtract the radius (i.e. the amount of space covered
+ * by TaskView).
+ */
+ public void setCornerRadius(float radius) {
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4c6456..8b4ac1a8dc79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@ import java.util.List;
*
* Note that most of the implementation here inherits from
* {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
*/
public class DevicePostureController {
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
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
new file mode 100644
index 000000000000..ac6e4c2a6521
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemProperties;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ /**
+ * When {@code true}, floating windows like PiP would auto move to the position
+ * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+ */
+ private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
+
+ /**
+ * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+ * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+ * See also {@link #getPreferredHalfInTabletopMode()}.
+ */
+ private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+ SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+ PREFERRED_TABLETOP_HALF_TOP,
+ PREFERRED_TABLETOP_HALF_BOTTOM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreferredTabletopHalf {}
+
+ public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+ public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+ private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if floating windows like PiP would auto move to the position
+ * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+ */
+ public boolean enableMoveFloatingWindowInTabletop() {
+ return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+ }
+
+ /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+ @PreferredTabletopHalf
+ public int getPreferredHalfInTabletopMode() {
+ return PREFER_TOP_HALF_IN_TABLETOP
+ ? PREFERRED_TABLETOP_HALF_TOP
+ : PREFERRED_TABLETOP_HALF_BOTTOM;
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 8ba785a1f03a..931cf0cee28c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -70,6 +70,8 @@ public class TvWindowMenuActionButton extends RelativeLayout {
setTextAndDescription(textResId);
}
typedArray.recycle();
+
+ setIsCustomCloseAction(false);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
new file mode 100644
index 000000000000..81423473171d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an update to bubbles state. This is passed through
+ * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render
+ * bubbles. This should be kept this as minimal as possible in terms of data.
+ */
+public class BubbleBarUpdate implements Parcelable {
+
+ public static final String BUNDLE_KEY = "update";
+
+ public boolean expandedChanged;
+ public boolean expanded;
+ @Nullable
+ public String selectedBubbleKey;
+ @Nullable
+ public BubbleInfo addedBubble;
+ @Nullable
+ public BubbleInfo updatedBubble;
+ @Nullable
+ public String suppressedBubbleKey;
+ @Nullable
+ public String unsupressedBubbleKey;
+
+ // This is only populated if bubbles have been removed.
+ public List<RemovedBubble> removedBubbles = new ArrayList<>();
+
+ // This is only populated if the order of the bubbles has changed.
+ public List<String> bubbleKeysInOrder = new ArrayList<>();
+
+ // This is only populated the first time a listener is connected so it gets the current state.
+ public List<BubbleInfo> currentBubbleList = new ArrayList<>();
+
+ public BubbleBarUpdate() {
+ }
+
+ public BubbleBarUpdate(Parcel parcel) {
+ expandedChanged = parcel.readBoolean();
+ expanded = parcel.readBoolean();
+ selectedBubbleKey = parcel.readString();
+ addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ suppressedBubbleKey = parcel.readString();
+ unsupressedBubbleKey = parcel.readString();
+ removedBubbles = parcel.readParcelableList(new ArrayList<>(),
+ RemovedBubble.class.getClassLoader());
+ parcel.readStringList(bubbleKeysInOrder);
+ currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
+ BubbleInfo.class.getClassLoader());
+ }
+
+ /**
+ * Returns whether anything has changed in this update.
+ */
+ public boolean anythingChanged() {
+ return expandedChanged
+ || selectedBubbleKey != null
+ || addedBubble != null
+ || updatedBubble != null
+ || !removedBubbles.isEmpty()
+ || !bubbleKeysInOrder.isEmpty()
+ || suppressedBubbleKey != null
+ || unsupressedBubbleKey != null
+ || !currentBubbleList.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ + " expanded=" + expanded
+ + " selectedBubbleKey=" + selectedBubbleKey
+ + " addedBubble=" + addedBubble
+ + " updatedBubble=" + updatedBubble
+ + " suppressedBubbleKey=" + suppressedBubbleKey
+ + " unsuppressedBubbleKey=" + unsupressedBubbleKey
+ + " removedBubbles=" + removedBubbles
+ + " bubbles=" + bubbleKeysInOrder
+ + " currentBubbleList=" + currentBubbleList
+ + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBoolean(expandedChanged);
+ parcel.writeBoolean(expanded);
+ parcel.writeString(selectedBubbleKey);
+ parcel.writeParcelable(addedBubble, flags);
+ parcel.writeParcelable(updatedBubble, flags);
+ parcel.writeString(suppressedBubbleKey);
+ parcel.writeString(unsupressedBubbleKey);
+ parcel.writeParcelableList(removedBubbles, flags);
+ parcel.writeStringList(bubbleKeysInOrder);
+ parcel.writeParcelableList(currentBubbleList, flags);
+ }
+
+ @NonNull
+ public static final Creator<BubbleBarUpdate> CREATOR =
+ new Creator<BubbleBarUpdate>() {
+ public BubbleBarUpdate createFromParcel(Parcel source) {
+ return new BubbleBarUpdate(source);
+ }
+ public BubbleBarUpdate[] newArray(int size) {
+ return new BubbleBarUpdate[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
new file mode 100644
index 000000000000..b0dea7231a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Contains information necessary to present a bubble.
+ */
+public class BubbleInfo implements Parcelable {
+
+ // TODO(b/269672147): needs a title string for a11y & that comes from notification
+ // TODO(b/269671451): needs whether the bubble is an 'important person' or not
+
+ private String mKey; // Same key as the Notification
+ private int mFlags; // Flags from BubbleMetadata
+ private String mShortcutId;
+ private int mUserId;
+ private String mPackageName;
+ /**
+ * All notification bubbles require a shortcut to be set on the notification, however, the
+ * app could still specify an Icon and PendingIntent to use for the bubble. In that case
+ * this icon will be populated. If the bubble is entirely shortcut based, this will be null.
+ */
+ @Nullable
+ private Icon mIcon;
+
+ public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon,
+ int userId, String packageName) {
+ mKey = key;
+ mFlags = flags;
+ mShortcutId = shortcutId;
+ mIcon = icon;
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ public BubbleInfo(Parcel source) {
+ mKey = source.readString();
+ mFlags = source.readInt();
+ mShortcutId = source.readString();
+ mIcon = source.readTypedObject(Icon.CREATOR);
+ mUserId = source.readInt();
+ mPackageName = source.readString();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public String getShortcutId() {
+ return mShortcutId;
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Whether this bubble is currently being hidden from the stack.
+ */
+ public boolean isBubbleSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API
+ * to
+ * hide the bubble when in the same content).
+ */
+ public boolean isBubbleSuppressable() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether the notification for this bubble is hidden from the shade.
+ */
+ public boolean isNotificationSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BubbleInfo)) return false;
+ BubbleInfo bubble = (BubbleInfo) o;
+ return Objects.equals(mKey, bubble.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mKey);
+ parcel.writeInt(mFlags);
+ parcel.writeString(mShortcutId);
+ parcel.writeTypedObject(mIcon, flags);
+ parcel.writeInt(mUserId);
+ parcel.writeString(mPackageName);
+ }
+
+ @NonNull
+ public static final Creator<BubbleInfo> CREATOR =
+ new Creator<BubbleInfo>() {
+ public BubbleInfo createFromParcel(Parcel source) {
+ return new BubbleInfo(source);
+ }
+
+ public BubbleInfo[] newArray(int size) {
+ return new BubbleInfo[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
new file mode 100644
index 000000000000..f90591b84b7e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a removed bubble, defining the key and reason the bubble was removed.
+ */
+public class RemovedBubble implements Parcelable {
+
+ private final String mKey;
+ private final int mRemovalReason;
+
+ public RemovedBubble(String key, int removalReason) {
+ mKey = key;
+ mRemovalReason = removalReason;
+ }
+
+ public RemovedBubble(Parcel parcel) {
+ mKey = parcel.readString();
+ mRemovalReason = parcel.readInt();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public int getRemovalReason() {
+ return mRemovalReason;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKey);
+ dest.writeInt(mRemovalReason);
+ }
+
+ @NonNull
+ public static final Creator<RemovedBubble> CREATOR =
+ new Creator<RemovedBubble>() {
+ public RemovedBubble createFromParcel(Parcel source) {
+ return new RemovedBubble(source);
+ }
+ public RemovedBubble[] newArray(int size) {
+ return new RemovedBubble[size];
+ }
+ };
+}
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 e44257ebef5c..56616cb6bd88 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
@@ -165,6 +165,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
t.remove(mGapBackgroundLeash);
mGapBackgroundLeash = null;
}
+ if (mScreenshot != null) {
+ t.remove(mScreenshot);
+ mScreenshot = null;
+ }
mHostLeash = null;
mIcon = null;
mResizingIconView = null;
@@ -248,11 +252,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
- if (mScreenshot != null) {
- if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
- mScreenshotAnimator.cancel();
- }
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ if (mScreenshot != null) {
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
@@ -322,6 +326,12 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ } else if (mScreenshot != null) {
+ t.remove(mScreenshot);
+ }
+
mTempRect.set(mOldBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -334,6 +344,12 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (screenshot == null || !screenshot.isValid()) return;
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ } else if (mScreenshot != null) {
+ t.remove(mScreenshot);
+ }
+
mScreenshot = screenshot;
t.reparent(screenshot, mHostLeash);
t.setLayer(screenshot, Integer.MAX_VALUE - 1);
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 b447a543989e..9eba5ecd36f1 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
@@ -738,6 +738,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
getRefBounds2(mTempRect);
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
+ // Make right or bottom side surface always higher than left or top side to avoid weird
+ // animation when dismiss split. e.g. App surface fling above on decor surface.
+ t.setLayer(leash1, 1);
+ t.setLayer(leash2, 2);
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
@@ -1116,7 +1120,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
"onImeStartPositioning");
- return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
+ return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 06f0a70d3d0f..09d99b204bdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -39,22 +39,48 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
"enable_letterbox_restart_confirmation_dialog";
private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
- "enable_letterbox_reachability_education";
+ "enable_letterbox_education_for_reachability";
private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG = true;
- private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = false;
+ private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = true;
/**
- * The name of the {@link SharedPreferences} that holds which user has seen the Restart
- * confirmation dialog.
+ * The name of the {@link SharedPreferences} that holds information about compat ui.
*/
- private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog";
+ private static final String COMPAT_UI_SHARED_PREFERENCES = "dont_show_restart_dialog";
/**
- * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}.
+ * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
+ * Education dialog.
*/
- private final SharedPreferences mSharedPreferences;
+ private static final String HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES =
+ "has_seen_letterbox_education";
+
+ /**
+ * Key prefix for the {@link SharedPreferences} entries related to the horizontal
+ * reachability education.
+ */
+ private static final String HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_horizontal_reachability_education";
+
+ /**
+ * Key prefix for the {@link SharedPreferences} entries related to the vertical reachability
+ * education.
+ */
+ private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_vertical_reachability_education";
+
+ /**
+ * The {@link SharedPreferences} instance for the restart dialog and the reachability
+ * education.
+ */
+ private final SharedPreferences mCompatUISharedPreferences;
+
+ /**
+ * The {@link SharedPreferences} instance for the letterbox education dialog.
+ */
+ private final SharedPreferences mLetterboxEduSharedPreferences;
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -88,8 +114,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
this);
- mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME,
+ mCompatUISharedPreferences = context.getSharedPreferences(getCompatUISharedPreferenceName(),
Context.MODE_PRIVATE);
+ mLetterboxEduSharedPreferences = context.getSharedPreferences(
+ getHasSeenLetterboxEducationSharedPreferencedName(), Context.MODE_PRIVATE);
}
/**
@@ -108,34 +136,72 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
}
/**
- * @return {@value true} if the reachability education is enabled.
- */
- boolean isReachabilityEducationEnabled() {
- return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
- && mIsLetterboxReachabilityEducationAllowed);
- }
-
- /**
* Enables/Disables the reachability education
*/
void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
mIsReachabilityEducationOverrideEnabled = enabled;
}
- boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) {
- final int userId = taskInfo.userId;
- final String packageName = taskInfo.topActivity.getPackageName();
- return mSharedPreferences.getBoolean(
- getDontShowAgainRestartKey(userId, packageName), /* default= */ false);
+ void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ dontShowAgainRestartKey(taskInfo.userId, taskInfo.topActivity.getPackageName()),
+ true).apply();
}
- void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
- final int userId = taskInfo.userId;
- final String packageName = taskInfo.topActivity.getPackageName();
- mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName),
+ boolean shouldShowRestartDialogAgain(TaskInfo taskInfo) {
+ return !mCompatUISharedPreferences.getBoolean(dontShowAgainRestartKey(taskInfo.userId,
+ taskInfo.topActivity.getPackageName()), /* default= */ false);
+ }
+
+ void setUserHasSeenHorizontalReachabilityEducation(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ void setUserHasSeenVerticalReachabilityEducation(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ boolean hasSeenHorizontalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), /* default= */false);
+ }
+
+ boolean hasSeenVerticalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), /* default= */false);
+ }
+
+ boolean shouldShowReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return isReachabilityEducationEnabled()
+ && (!hasSeenHorizontalReachabilityEducation(taskInfo)
+ || !hasSeenVerticalReachabilityEducation(taskInfo));
+ }
+
+ boolean getHasSeenLetterboxEducation(int userId) {
+ return mLetterboxEduSharedPreferences
+ .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
+ }
+
+ void setSeenLetterboxEducation(int userId) {
+ mLetterboxEduSharedPreferences.edit().putBoolean(dontShowLetterboxEduKey(userId),
true).apply();
}
+ protected String getCompatUISharedPreferenceName() {
+ return COMPAT_UI_SHARED_PREFERENCES;
+ }
+
+ protected String getHasSeenLetterboxEducationSharedPreferencedName() {
+ return HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES;
+ }
+
+ /**
+ * Updates the {@link DeviceConfig} state for the CompatUI
+ * @param properties Contains the complete collection of properties which have changed for a
+ * single namespace. This includes only those which were added, updated,
+ */
@Override
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
@@ -152,7 +218,24 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
}
}
- private String getDontShowAgainRestartKey(int userId, String packageName) {
+ private boolean isReachabilityEducationEnabled() {
+ return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+ && mIsLetterboxReachabilityEducationAllowed);
+ }
+
+ private static String hasSeenHorizontalReachabilityEduKey(int userId) {
+ return HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String hasSeenVerticalReachabilityEduKey(int userId) {
+ return HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String dontShowLetterboxEduKey(int userId) {
+ return String.valueOf(userId);
+ }
+
+ private String dontShowAgainRestartKey(int userId, String packageName) {
return packageName + "@" + userId;
}
} \ No newline at end of file
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 76d9152fdfbc..838e37a905db 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
@@ -42,7 +42,6 @@ import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -113,6 +112,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
@Nullable
private LetterboxEduWindowManager mActiveLetterboxEduLayout;
+ /**
+ * The active Reachability UI layout.
+ */
+ @Nullable
+ private ReachabilityEduWindowManager mActiveReachabilityEduLayout;
+
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -196,6 +201,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
+ if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ }
}
@Override
@@ -309,7 +317,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void onRestartButtonClicked(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) {
if (mCompatUIConfiguration.isRestartDialogEnabled()
- && !mCompatUIConfiguration.getDontShowRestartDialogAgain(
+ && mCompatUIConfiguration.shouldShowRestartDialogAgain(
taskInfoState.first)) {
// We need to show the dialog
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
@@ -356,13 +364,15 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mTransitionsLazy.get(),
- this::onLetterboxEduDismissed,
- mDockStateReader);
+ mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader,
+ mCompatUIConfiguration);
}
- private void onLetterboxEduDismissed() {
+ private void onLetterboxEduDismissed(
+ Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mActiveLetterboxEduLayout = null;
+ // We need to update the UI
+ createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
}
private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
@@ -420,6 +430,46 @@ public class CompatUIController implements OnDisplaysChangedListener,
onCompatInfoChanged(stateInfo.first, stateInfo.second);
}
+ private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ if (mActiveReachabilityEduLayout != null) {
+ // UI already exists, update the UI layout.
+ if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, remove from active layouts.
+ mActiveReachabilityEduLayout = null;
+ }
+ return;
+ }
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ ReachabilityEduWindowManager newLayout = createReachabilityEduWindowManager(context,
+ taskInfo, taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, make it the active layout.
+ if (mActiveReachabilityEduLayout != null) {
+ // Release the previous layout since at most one can be active.
+ // Since letterbox reachability education is only shown once to the user,
+ // releasing the previous layout is only a precaution.
+ mActiveReachabilityEduLayout.release();
+ }
+ mActiveReachabilityEduLayout = newLayout;
+ }
+ }
+
+ @VisibleForTesting
+ ReachabilityEduWindowManager createReachabilityEduWindowManager(Context context,
+ TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
+ taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mCompatUIConfiguration, mMainExecutor);
+ }
+
+
private void removeLayouts(int taskId) {
final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
if (layout != null) {
@@ -439,6 +489,11 @@ public class CompatUIController implements OnDisplaysChangedListener,
mTaskIdToRestartDialogWindowManagerMap.remove(taskId);
mSetOfTaskIdsShowingRestartDialog.remove(taskId);
}
+ if (mActiveReachabilityEduLayout != null
+ && mActiveReachabilityEduLayout.getTaskId() == taskId) {
+ mActiveReachabilityEduLayout.release();
+ mActiveReachabilityEduLayout = null;
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -491,6 +546,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
callback.accept(layout);
}
}
+ if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
+ callback.accept(mActiveReachabilityEduLayout);
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index d44b4d8f63b6..f65c26ada04d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -21,6 +21,7 @@ import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -112,6 +113,14 @@ class CompatUILayout extends LinearLayout {
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mWindowManager.relayout();
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
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 fe95d04bad3c..170c0ee91b40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,7 +38,6 @@ 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.letterboxedu.LetterboxEduWindowManager;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index b22c9c7e7529..9c4e79cd631b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -384,7 +384,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
// Cannot be wrap_content as this determines the actual window size
width, height,
TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+ getWindowManagerLayoutParamsFlags(),
PixelFormat.TRANSLUCENT);
winParams.token = new Binder();
winParams.setTitle(getClass().getSimpleName() + mTaskId);
@@ -392,6 +392,13 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
return winParams;
}
+ /**
+ * @return Flags to use for the {@link WindowManager} layout
+ */
+ protected int getWindowManagerLayoutParamsFlags() {
+ return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
+ }
+
protected final String getTag() {
return getClass().getSimpleName();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
index 02197f644a39..9974295123b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
index 9232f36cf939..df2f6ce24ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,7 +26,6 @@ import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.wm.shell.R;
-import com.android.wm.shell.compatui.DialogContainerSupplier;
/**
* Container for Letterbox Education Dialog and background dim.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index c14c009721a1..0c21c8ccd686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Rect;
import android.provider.Settings;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
@@ -36,14 +37,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Window manager for the Letterbox Education.
*/
-public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
+class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
/**
* The Letterbox Education should be the topmost child of the Task in case there can be more
@@ -51,19 +52,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
public static final int Z_ORDER = Integer.MAX_VALUE;
- /**
- * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
- * Education dialog.
- */
- @VisibleForTesting
- static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
- "has_seen_letterbox_education";
-
- /**
- * The {@link SharedPreferences} instance for {@link #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}.
- */
- private final SharedPreferences mSharedPreferences;
-
private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
private final Transitions mTransitions;
@@ -75,6 +63,10 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
private final int mUserId;
+ private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
// Remember the last reported state in case visibility changes due to keyguard or IME updates.
private boolean mEligibleForLetterboxEducation;
@@ -82,7 +74,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@VisibleForTesting
LetterboxEduDialogLayout mLayout;
- private final Runnable mOnDismissCallback;
+ @NonNull
+ private TaskInfo mTaskInfo;
/**
* The vertical margin between the dialog container and the task stable bounds (excluding
@@ -92,33 +85,35 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
private final DockStateReader mDockStateReader;
- public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions,
- Runnable onDismissCallback, DockStateReader dockStateReader) {
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
onDismissCallback,
new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
- dockStateReader);
+ dockStateReader, compatUIConfiguration);
}
@VisibleForTesting
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
- DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
DialogAnimationController<LetterboxEduDialogLayout> animationController,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
mAnimationController = animationController;
mUserId = taskInfo.userId;
- mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
- mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
- Context.MODE_PRIVATE);
mDialogVerticalMargin = (int) mContext.getResources().getDimension(
R.dimen.letterbox_education_dialog_margin);
mDockStateReader = dockStateReader;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
}
@Override
@@ -144,8 +139,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
// the controller will create a new instance of this class since this one isn't eligible).
// - If the layout isn't null then it was previously showing, and we shouldn't check if the
// user has seen the letterbox education before.
- return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
- && (mLayout != null || !getHasSeenLetterboxEducation())
+ return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
+ || !mCompatUIConfiguration.getHasSeenLetterboxEducation(mUserId))
&& !mDockStateReader.isDocked();
}
@@ -194,7 +189,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
// Dialog has already been released.
return;
}
- setSeenLetterboxEducation();
mLayout.setDismissOnClickListener(this::onDismiss);
// Focus on the dialog title for accessibility.
mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
@@ -204,10 +198,11 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
if (mLayout == null) {
return;
}
+ mCompatUIConfiguration.setSeenLetterboxEducation(mUserId);
mLayout.setDismissOnClickListener(null);
mAnimationController.startExitAnimation(mLayout, () -> {
release();
- mOnDismissCallback.run();
+ mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
});
}
@@ -220,6 +215,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
+ mTaskInfo = taskInfo;
mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
return super.updateCompatInfo(taskInfo, taskListener, canShow);
@@ -250,18 +246,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
taskBounds.height());
}
- private boolean getHasSeenLetterboxEducation() {
- return mSharedPreferences.getBoolean(getPrefKey(), /* default= */ false);
- }
-
- private void setSeenLetterboxEducation() {
- mSharedPreferences.edit().putBoolean(getPrefKey(), true).apply();
- }
-
- private String getPrefKey() {
- return String.valueOf(mUserId);
- }
-
@VisibleForTesting
boolean isTaskbarEduShowing() {
return Settings.Secure.getInt(mContext.getContentResolver(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
new file mode 100644
index 000000000000..dc3d1d35fecb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * Container for reachability education which handles all the show/hide animations.
+ */
+public class ReachabilityEduLayout extends FrameLayout {
+
+ private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+ private static final float ALPHA_FULL_OPAQUE = 1f;
+
+ private static final long VISIBILITY_ANIMATION_DURATION_MS = 400;
+
+ private static final long MARGINS_ANIMATION_DURATION_MS = 250;
+
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+ private ReachabilityEduWindowManager mWindowManager;
+
+ private View mMoveLeftButton;
+ private View mMoveRightButton;
+ private View mMoveUpButton;
+ private View mMoveDownButton;
+
+ private int mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ private int mLastRightMargin = PROPERTY_VALUE_UNSET;
+ private int mLastTopMargin = PROPERTY_VALUE_UNSET;
+ private int mLastBottomMargin = PROPERTY_VALUE_UNSET;
+
+ public ReachabilityEduLayout(Context context) {
+ this(context, null);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(ReachabilityEduWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ void handleVisibility(boolean horizontalEnabled, boolean verticalEnabled,
+ int letterboxVerticalPosition,
+ int letterboxHorizontalPosition, int availableWidth, int availableHeight,
+ CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo) {
+ hideAllImmediately();
+ if (horizontalEnabled && letterboxHorizontalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition);
+ compatUIConfiguration.setUserHasSeenHorizontalReachabilityEducation(taskInfo);
+ } else if (verticalEnabled && letterboxVerticalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition);
+ compatUIConfiguration.setUserHasSeenVerticalReachabilityEducation(taskInfo);
+ }
+ }
+
+ void hideAllImmediately() {
+ hideImmediately(mMoveLeftButton);
+ hideImmediately(mMoveRightButton);
+ hideImmediately(mMoveUpButton);
+ hideImmediately(mMoveDownButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMoveLeftButton = findViewById(R.id.reachability_move_left_button);
+ mMoveRightButton = findViewById(R.id.reachability_move_right_button);
+ mMoveUpButton = findViewById(R.id.reachability_move_up_button);
+ mMoveDownButton = findViewById(R.id.reachability_move_down_button);
+ mMoveLeftButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveRightButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveUpButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveDownButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+
+ private void hideImmediately(View view) {
+ view.setAlpha(0);
+ view.setVisibility(View.INVISIBLE);
+ }
+
+ private Animator marginAnimator(View view, Function<LayoutParams, Integer> marginSupplier,
+ BiConsumer<LayoutParams, Integer> marginConsumer, int from, int to) {
+ final LayoutParams layoutParams = ((LayoutParams) view.getLayoutParams());
+ ValueAnimator animator = ValueAnimator.ofInt(marginSupplier.apply(layoutParams), from, to);
+ animator.addUpdateListener(valueAnimator -> {
+ marginConsumer.accept(layoutParams, (Integer) valueAnimator.getAnimatedValue());
+ view.requestLayout();
+ });
+ animator.setDuration(MARGINS_ANIMATION_DURATION_MS);
+ return animator;
+ }
+
+ private void handleLetterboxHorizontalPosition(int availableWidth,
+ int letterboxHorizontalPosition) {
+ hideItem(mMoveUpButton);
+ hideItem(mMoveDownButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ // We calculate the available space on the left and right
+ final int horizontalGap = availableWidth / 2;
+ final int leftAvailableSpace = letterboxHorizontalPosition * horizontalGap;
+ final int rightAvailableSpace = availableWidth - leftAvailableSpace;
+ // We show the button if we have enough space
+ if (leftAvailableSpace >= mMoveLeftButton.getMeasuredWidth()) {
+ int newLeftMargin = (horizontalGap - mMoveLeftButton.getMeasuredWidth()) / 2;
+ if (mLastLeftMargin == PROPERTY_VALUE_UNSET) {
+ mLastLeftMargin = newLeftMargin;
+ }
+ if (mLastLeftMargin != newLeftMargin) {
+ marginAnimator(mMoveLeftButton, layoutParams -> layoutParams.leftMargin,
+ (layoutParams, margin) -> layoutParams.leftMargin = margin,
+ mLastLeftMargin, newLeftMargin).start();
+ } else {
+ final LayoutParams leftParams = ((LayoutParams) mMoveLeftButton.getLayoutParams());
+ leftParams.leftMargin = mLastLeftMargin;
+ mMoveLeftButton.setLayoutParams(leftParams);
+ }
+ showItem(mMoveLeftButton);
+ } else {
+ hideItem(mMoveLeftButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ }
+ if (rightAvailableSpace >= mMoveRightButton.getMeasuredWidth()) {
+ int newRightMargin = (horizontalGap - mMoveRightButton.getMeasuredWidth()) / 2;
+ if (mLastRightMargin == PROPERTY_VALUE_UNSET) {
+ mLastRightMargin = newRightMargin;
+ }
+ if (mLastRightMargin != newRightMargin) {
+ marginAnimator(mMoveRightButton, layoutParams -> layoutParams.rightMargin,
+ (layoutParams, margin) -> layoutParams.rightMargin = margin,
+ mLastRightMargin, newRightMargin).start();
+ } else {
+ final LayoutParams rightParams =
+ ((LayoutParams) mMoveRightButton.getLayoutParams());
+ rightParams.rightMargin = mLastRightMargin;
+ mMoveRightButton.setLayoutParams(rightParams);
+ }
+ showItem(mMoveRightButton);
+ } else {
+ hideItem(mMoveRightButton);
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ }
+ }
+
+ private void handleLetterboxVerticalPosition(int availableHeight,
+ int letterboxVerticalPosition) {
+ hideItem(mMoveLeftButton);
+ hideItem(mMoveRightButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ // We calculate the available space on the left and right
+ final int verticalGap = availableHeight / 2;
+ final int topAvailableSpace = letterboxVerticalPosition * verticalGap;
+ final int bottomAvailableSpace = availableHeight - topAvailableSpace;
+ if (topAvailableSpace >= mMoveUpButton.getMeasuredHeight()) {
+ int newTopMargin = (verticalGap - mMoveUpButton.getMeasuredHeight()) / 2;
+ if (mLastTopMargin == PROPERTY_VALUE_UNSET) {
+ mLastTopMargin = newTopMargin;
+ }
+ if (mLastTopMargin != newTopMargin) {
+ marginAnimator(mMoveUpButton, layoutParams -> layoutParams.topMargin,
+ (layoutParams, margin) -> layoutParams.topMargin = margin,
+ mLastTopMargin, newTopMargin).start();
+ } else {
+ final LayoutParams topParams = ((LayoutParams) mMoveUpButton.getLayoutParams());
+ topParams.topMargin = mLastTopMargin;
+ mMoveUpButton.setLayoutParams(topParams);
+ }
+ showItem(mMoveUpButton);
+ } else {
+ hideItem(mMoveUpButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ }
+ if (bottomAvailableSpace >= mMoveDownButton.getMeasuredHeight()) {
+ int newBottomMargin = (verticalGap - mMoveDownButton.getMeasuredHeight()) / 2;
+ if (mLastBottomMargin == PROPERTY_VALUE_UNSET) {
+ mLastBottomMargin = newBottomMargin;
+ }
+ if (mLastBottomMargin != newBottomMargin) {
+ marginAnimator(mMoveDownButton, layoutParams -> layoutParams.bottomMargin,
+ (layoutParams, margin) -> layoutParams.bottomMargin = margin,
+ mLastBottomMargin, newBottomMargin).start();
+ } else {
+ final LayoutParams bottomParams =
+ ((LayoutParams) mMoveDownButton.getLayoutParams());
+ bottomParams.bottomMargin = mLastBottomMargin;
+ mMoveDownButton.setLayoutParams(bottomParams);
+ }
+ showItem(mMoveDownButton);
+ } else {
+ hideItem(mMoveDownButton);
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ }
+ }
+
+ private void showItem(View view) {
+ view.setVisibility(View.VISIBLE);
+ ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeIn.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ fadeIn.start();
+ }
+
+ private void hideItem(View view) {
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
+ fadeOut.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ fadeOut.start();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
new file mode 100644
index 000000000000..b6e396d12512
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Window manager for the reachability education
+ */
+class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
+
+ /**
+ * The Compat UI should be below the Letterbox Education.
+ */
+ private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
+
+ // The time to wait before hiding the education
+ private static final long DISAPPEAR_DELAY_MS = 4000L;
+
+ private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
+ private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ private final ShellExecutor mMainExecutor;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
+ private boolean mIsActivityLetterboxed;
+
+ private int mLetterboxVerticalPosition;
+
+ private int mLetterboxHorizontalPosition;
+
+ private int mTopActivityLetterboxWidth;
+
+ private int mTopActivityLetterboxHeight;
+
+ private long mNextHideTime = -1L;
+
+ private boolean mForceUpdate = false;
+
+ // We decided to force the visualization of the double-tap animated icons every time the user
+ // double-taps.
+ private boolean mHasUserDoubleTapped;
+
+ // When the size of the letterboxed app changes and the icons are visible
+ // we need to animate them.
+ private boolean mHasLetterboxSizeChanged;
+
+ @Nullable
+ @VisibleForTesting
+ ReachabilityEduLayout mLayout;
+
+ ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue,
+ ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+ CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
+ mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mMainExecutor = mainExecutor;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return Z_ORDER;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mIsActivityLetterboxed
+ && (mLetterboxVerticalPosition != -1 || mLetterboxHorizontalPosition != -1);
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
+
+ updateVisibilityOfViews();
+
+ return mLayout;
+ }
+
+ @VisibleForTesting
+ ReachabilityEduLayout inflateLayout() {
+ return (ReachabilityEduLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.reachability_ui_layout, null);
+ }
+
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ mTaskInfo = taskInfo;
+ final boolean prevIsActivityLetterboxed = mIsActivityLetterboxed;
+ final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition;
+ final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
+ final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
+ final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
+ mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ mHasUserDoubleTapped = taskInfo.isFromLetterboxDoubleTap;
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
+ }
+
+ mHasLetterboxSizeChanged = prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
+ || prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight;
+
+ if (mHasUserDoubleTapped || prevIsActivityLetterboxed != mIsActivityLetterboxed
+ || prevLetterboxVerticalPosition != mLetterboxVerticalPosition
+ || prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition
+ || prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
+ || prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight) {
+ updateVisibilityOfViews();
+ }
+
+ return true;
+ }
+
+ void forceUpdate(boolean forceUpdate) {
+ mForceUpdate = forceUpdate;
+ }
+
+ @Override
+ protected void onParentBoundsChanged() {
+ if (mLayout == null) {
+ return;
+ }
+ // Both the layout dimensions and dialog margins depend on the parent bounds.
+ WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
+ mLayout.setLayoutParams(windowLayoutParams);
+ relayout(windowLayoutParams);
+ }
+
+ /** Gets the layout params. */
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
+ View layout = getLayout();
+ if (layout == null) {
+ return new WindowManager.LayoutParams();
+ }
+ // Measure how big the hint is since its size depends on the text size.
+ final Rect taskBounds = getTaskBounds();
+ layout.measure(View.MeasureSpec.makeMeasureSpec(taskBounds.width(),
+ View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskBounds.height(),
+ View.MeasureSpec.EXACTLY));
+ return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight());
+ }
+
+ /**
+ * @return Flags to use for the WindowManager layout
+ */
+ @Override
+ protected int getWindowManagerLayoutParamsFlags() {
+ return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE;
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
+ return;
+ }
+ updateSurfacePosition(0, 0);
+ }
+
+ void updateHideTime() {
+ mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS;
+ }
+
+ private void updateVisibilityOfViews() {
+ if (mLayout == null) {
+ return;
+ }
+
+ final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+ final boolean eligibleForDisplayVerticalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+
+ if (mIsActivityLetterboxed && (eligibleForDisplayHorizontalEducation
+ || eligibleForDisplayVerticalEducation)) {
+ int availableWidth = getTaskBounds().width() - mTopActivityLetterboxWidth;
+ int availableHeight = getTaskBounds().height() - mTopActivityLetterboxHeight;
+ mLayout.handleVisibility(eligibleForDisplayHorizontalEducation,
+ eligibleForDisplayVerticalEducation,
+ mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth,
+ availableHeight, mCompatUIConfiguration, mTaskInfo);
+ if (!mHasLetterboxSizeChanged) {
+ updateHideTime();
+ mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+ }
+ mHasUserDoubleTapped = false;
+ } else {
+ mLayout.hideAllImmediately();
+ }
+ }
+
+ private void hideReachability() {
+ if (mLayout == null || !shouldHideEducation()) {
+ return;
+ }
+ mLayout.hideAllImmediately();
+ }
+
+ private boolean shouldHideEducation() {
+ return SystemClock.uptimeMillis() >= mNextHideTime;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
index 2440838844c4..aab123a843ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -130,7 +130,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
protected boolean eligibleToShowLayout() {
// We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
- || !mCompatUIConfiguration.getDontShowRestartDialogAgain(mTaskInfo));
+ || mCompatUIConfiguration.shouldShowRestartDialogAgain(mTaskInfo));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index ab968563854c..14daae03e6b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -70,7 +71,7 @@ public abstract class TvPipModule {
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -91,7 +92,7 @@ public abstract class TvPipModule {
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -141,28 +142,36 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new TvPipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@WMSingleton
@Provides
static PipTransitionController provideTvPipTransition(
+ Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
- PipAnimationController pipAnimationController,
+ TvPipBoundsState tvPipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipTransitionState pipTransitionState,
+ TvPipMenuController pipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
- TvPipBoundsState tvPipBoundsState, TvPipMenuController pipMenuController) {
- return new TvPipTransition(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
- pipMenuController, tvPipBoundsAlgorithm, pipAnimationController);
+ PipAnimationController pipAnimationController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+ tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ Optional.empty());
}
@WMSingleton
@@ -203,7 +212,7 @@ public abstract class TvPipModule {
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -215,7 +224,7 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new TvPipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
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 838022536e43..9808c591592f 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
@@ -32,9 +32,6 @@ import com.android.wm.shell.R;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewFactory;
-import com.android.wm.shell.TaskViewFactoryController;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
@@ -52,6 +49,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -81,6 +79,7 @@ import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -91,6 +90,9 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellInterface;
+import com.android.wm.shell.taskview.TaskViewFactory;
+import com.android.wm.shell.taskview.TaskViewFactoryController;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -172,6 +174,18 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
@@ -507,6 +521,9 @@ public abstract class WMShellBaseModule {
desktopModeTaskRepository, mainExecutor));
}
+ @BindsOptionalOf
+ abstract RecentsTransitionHandler optionalRecentsTransitionHandler();
+
//
// Shell transitions
//
@@ -790,6 +807,7 @@ public abstract class WMShellBaseModule {
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
Optional<OneHandedController> oneHandedControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
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 948bf2d100f9..d8e2f5c4a817 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
@@ -30,7 +30,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -45,6 +44,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -53,6 +53,8 @@ import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -65,6 +67,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -81,10 +84,12 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -344,6 +349,7 @@ public abstract class WMShellModule {
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -355,23 +361,24 @@ public abstract class WMShellModule {
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, displayInsetsController,
- oneHandedController, mainExecutor));
+ pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
}
@WMSingleton
@Provides
static PipBoundsState providePipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new PipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@@ -388,8 +395,9 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
@WMSingleton
@@ -446,7 +454,7 @@ public abstract class WMShellModule {
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -458,7 +466,7 @@ public abstract class WMShellModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
@@ -477,12 +485,12 @@ public abstract class WMShellModule {
static PipTransitionController providePipTransitionController(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+ PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+ pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
splitScreenOptional);
}
@@ -523,9 +531,20 @@ public abstract class WMShellModule {
ShellInit shellInit,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional);
+ pipTouchHandlerOptional, recentsTransitionHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static RecentsTransitionHandler provideRecentsTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ Optional<RecentTasksController> recentTasksController) {
+ return new RecentsTransitionHandler(shellInit, transitions,
+ recentTasksController.orElse(null));
}
//
@@ -654,13 +673,36 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
ShellController shellController,
+ DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
+ EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
+ ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
- transitions, desktopModeTaskRepository, mainExecutor);
+ return new DesktopTasksController(context, shellInit, shellController, displayController,
+ shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
+ enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+ desktopModeTaskRepository, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler(
+ Transitions transitions) {
+ return new EnterDesktopTaskTransitionHandler(transitions);
+ }
+
+ @WMSingleton
+ @Provides
+ static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Context context
+ ) {
+ return new ExitDesktopTaskTransitionHandler(transitions, context);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index c9c0e40f616c..2bdbde1b71d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -41,7 +41,6 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
-import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DisplayAreaInfo;
@@ -313,6 +312,20 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
/**
+ * Moves a specifc task to the front.
+ * @param taskInfo the task to show in front.
+ */
+ public void moveTaskToFront(RunningTaskInfo taskInfo) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(taskInfo.token, true /* onTop */);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
+ } else {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ /**
* Turn desktop mode on or off
* @param active the desired state for desktop mode setting
*/
@@ -364,10 +377,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
- Pair<Transitions.TransitionHandler, WindowContainerTransaction> subHandler =
- mTransitions.dispatchRequest(transition, request, this);
- WindowContainerTransaction wct = subHandler != null
- ? subHandler.second : new WindowContainerTransaction();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
bringDesktopAppsToFront(wct);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
new file mode 100644
index 000000000000..fb0a91f17802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Animated visual indicator for Desktop Mode windowing transitions.
+ */
+public class DesktopModeVisualIndicator {
+
+ private final Context mContext;
+ private final DisplayController mDisplayController;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
+ private final ActivityManager.RunningTaskInfo mTaskInfo;
+ private final SurfaceControl mTaskSurface;
+ private SurfaceControl mLeash;
+
+ private final SyncTransactionQueue mSyncQueue;
+ private SurfaceControlViewHost mViewHost;
+
+ private View mView;
+ private boolean mIsFullscreen;
+
+ public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+ ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
+ Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+ mSyncQueue = syncQueue;
+ mTaskInfo = taskInfo;
+ mDisplayController = displayController;
+ mContext = context;
+ mTaskSurface = taskSurface;
+ mTaskOrganizer = taskOrganizer;
+ mRootTdaOrganizer = taskDisplayAreaOrganizer;
+ createView();
+ }
+
+ /**
+ * Create a fullscreen indicator with no animation
+ */
+ private void createView() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+ mView = new View(mContext);
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+ mLeash = builder
+ .setName("Fullscreen Indicator")
+ .setContainerLayer()
+ .build();
+ t.show(mLeash);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(screenWidth, screenHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ final WindowlessWindowManager windowManager = new WindowlessWindowManager(
+ mTaskInfo.configuration, mLeash,
+ null /* hostInputToken */);
+ mViewHost = new SurfaceControlViewHost(mContext,
+ mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
+ "FullscreenVisualIndicator");
+ mViewHost.setView(mView, lp);
+ // We want this indicator to be behind the dragged task, but in front of all others.
+ t.setRelativeLayer(mLeash, mTaskSurface, -1);
+
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+
+ /**
+ * Create fullscreen indicator and fades it in.
+ */
+ public void createFullscreenIndicator() {
+ mIsFullscreen = true;
+ mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
+ */
+ public void createFullscreenIndicatorWithAnimatedBounds() {
+ mIsFullscreen = true;
+ mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+ .toFullscreenAnimatorWithAnimatedBounds(mView,
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Takes existing fullscreen indicator and animates it to freeform bounds
+ */
+ public void transitionFullscreenIndicatorToFreeform() {
+ mIsFullscreen = false;
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Takes the existing freeform indicator and animates it to fullscreen
+ */
+ public void transitionFreeformIndicatorToFullscreen() {
+ mIsFullscreen = true;
+ final VisualIndicatorAnimator animator =
+ VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Release the indicator and its components when it is no longer needed.
+ */
+ public void releaseVisualIndicator() {
+ if (mViewHost == null) return;
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.remove(mLeash);
+ mLeash = null;
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+ }
+
+ /**
+ * Returns true if visual indicator is fullscreen
+ */
+ public boolean isFullscreen() {
+ return mIsFullscreen;
+ }
+
+ /**
+ * Animator for Desktop Mode transitions which supports bounds and alpha animation.
+ */
+ private static class VisualIndicatorAnimator extends ValueAnimator {
+ private static final int FULLSCREEN_INDICATOR_DURATION = 200;
+ private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
+ private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+
+ private final View mView;
+ private final Rect mStartBounds;
+ private final Rect mEndBounds;
+ private final RectEvaluator mRectEvaluator;
+
+ private VisualIndicatorAnimator(View view, Rect startBounds,
+ Rect endBounds) {
+ mView = view;
+ mStartBounds = new Rect(startBounds);
+ mEndBounds = endBounds;
+ setFloatValues(0, 1);
+ mRectEvaluator = new RectEvaluator(new Rect());
+ }
+
+ /**
+ * Create animator for visual indicator of fullscreen transition
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
+ */
+ public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
+ @NonNull DisplayLayout displayLayout) {
+ final Rect bounds = getMaxBounds(displayLayout);
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, bounds, bounds);
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+
+ /**
+ * Create animator for visual indicator of fullscreen transition
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
+ */
+ public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
+ @NonNull View view, @NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ Rect startBounds = new Rect(padding, padding,
+ displayLayout.width() - padding, displayLayout.height() - padding);
+ view.getBackground().setBounds(startBounds);
+
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, startBounds, getMaxBounds(displayLayout));
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+ /**
+ * Create animator for visual indicator of freeform transition
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
+ */
+ public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
+ @NonNull DisplayLayout displayLayout) {
+ final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+ final int width = displayLayout.width();
+ final int height = displayLayout.height();
+ Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
+ (int) (adjustmentPercentage * height / 2),
+ (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
+ (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, getMaxBounds(displayLayout), endBounds);
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+ /**
+ * Add necessary listener for animation of indicator
+ */
+ private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
+ animator.addUpdateListener(a -> {
+ if (animator.mView != null) {
+ animator.updateBounds(a.getAnimatedFraction(), animator.mView);
+ animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ } else {
+ animator.cancel();
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animator.mView.getBackground().setBounds(animator.mEndBounds);
+ }
+ });
+ animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
+ }
+
+ /**
+ * Update bounds of view based on current animation fraction.
+ * Use of delta is to animate bounds independently, in case we need to
+ * run multiple animations simultaneously.
+ *
+ * @param fraction fraction to use, compared against previous fraction
+ * @param view the view to update
+ */
+ private void updateBounds(float fraction, View view) {
+ if (mStartBounds.equals(mEndBounds)) {
+ return;
+ }
+ Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+ view.getBackground().setBounds(currentBounds);
+ }
+
+ /**
+ * Fade in the fullscreen indicator
+ *
+ * @param fraction current animation fraction
+ */
+ private void updateIndicatorAlpha(float fraction, View view) {
+ view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
+ }
+
+ /**
+ * Return the max bounds of a fullscreen indicator
+ */
+ private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ final int width = displayLayout.width() - 2 * padding;
+ final int height = displayLayout.height() - 2 * padding;
+ Rect endBounds = new Rect((int) (padding
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (padding
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
+ (int) (displayLayout.width() - padding
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (displayLayout.height() - padding
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
+ return endBounds;
+ }
+ }
+}
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 73a740381090..0400963a47e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -24,7 +25,10 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
import android.os.IBinder
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -32,14 +36,18 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
@@ -53,16 +61,22 @@ import java.util.function.Consumer
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
- private val context: Context,
- shellInit: ShellInit,
- private val shellController: ShellController,
- private val shellTaskOrganizer: ShellTaskOrganizer,
- private val transitions: Transitions,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
- @ShellMainThread private val mainExecutor: ShellExecutor
+ private val context: Context,
+ shellInit: ShellInit,
+ private val shellController: ShellController,
+ private val displayController: DisplayController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val transitions: Transitions,
+ private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
+ private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ @ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
private val desktopMode: DesktopModeImpl
+ private var visualIndicator: DesktopModeVisualIndicator? = null
init {
desktopMode = DesktopModeImpl()
@@ -109,18 +123,54 @@ class DesktopTasksController(
}
/** Move a task to desktop */
- fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+ fun moveToDesktop(task: RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(wct)
+ addMoveToDesktopChanges(wct, task.token)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
- wct.reorder(task.getToken(), true /* onTop */)
+ /**
+ * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
+ * startBounds
+ */
+ fun moveToFreeform(
+ taskInfo: RunningTaskInfo,
+ startBounds: Rect
+ ) {
+ val wct = WindowContainerTransaction()
+ moveHomeTaskToFront(wct)
+ addMoveToDesktopChanges(wct, taskInfo.getToken())
+ wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ enterDesktopTaskTransitionHandler.startTransition(
+ Transitions.TRANSIT_ENTER_FREEFORM, wct)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /** Brings apps to front and sets freeform task bounds */
+ fun moveToDesktopWithAnimation(
+ taskInfo: RunningTaskInfo,
+ freeformBounds: Rect
+ ) {
+ val wct = WindowContainerTransaction()
+ bringDesktopAppsToFront(wct)
+ addMoveToDesktopChanges(wct, taskInfo.getToken())
+ wct.setBounds(taskInfo.token, freeformBounds)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ enterDesktopTaskTransitionHandler.startTransition(
+ Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -132,12 +182,11 @@ class DesktopTasksController(
}
/** Move a task to fullscreen */
- fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+ fun moveToFullscreen(task: RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(task.getToken(), null)
+ addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -145,6 +194,44 @@ class DesktopTasksController(
}
}
+
+ /**
+ * Move a task to fullscreen after being dragged from fullscreen and released back into
+ * status bar area
+ */
+ fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) {
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task.token)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task.token)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ exitDesktopTaskTransitionHandler.startTransition(
+ Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /** Move a task to the front **/
+ fun moveTaskToFront(taskInfo: RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ wct.reorder(taskInfo.token, true)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* Get windowing move for a given `taskId`
*
@@ -202,7 +289,7 @@ class DesktopTasksController(
request: TransitionRequestInfo
): WindowContainerTransaction? {
// Check if we should skip handling this transition
- val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+ val task: RunningTaskInfo? = request.triggerTask
val shouldHandleRequest =
when {
// Only handle open or to front transitions
@@ -234,8 +321,8 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToDesktopChanges(wct, task.token)
}
}
}
@@ -251,15 +338,44 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
- setBounds(task.token, null)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task.token)
}
}
}
return null
}
+ private fun addMoveToDesktopChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+ wct.reorder(token, true /* onTop */)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getDesktopDensityDpi())
+ }
+ }
+
+ private fun addMoveToFullscreenChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(token, null)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getFullscreenDensityDpi())
+ }
+ }
+
+ private fun getFullscreenDensityDpi(): Int {
+ return context.resources.displayMetrics.densityDpi
+ }
+
+ private fun getDesktopDensityDpi(): Int {
+ return DESKTOP_DENSITY_OVERRIDE
+ }
+
/** Creates a new instance of the external interface to pass to another process. */
private fun createExternalInterface(): ExternalInterfaceBinder {
return IDesktopModeImpl(this)
@@ -271,6 +387,108 @@ class DesktopTasksController(
}
/**
+ * Perform checks required on drag move. Create/release fullscreen indicator as needed.
+ *
+ * @param taskInfo the task being dragged.
+ * @param taskSurface SurfaceControl of dragged task.
+ * @param y coordinate of dragged task. Used for checks against status bar height.
+ */
+ fun onDragPositioningMove(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ y: Float
+ ) {
+ if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ val statusBarHeight = getStatusBarHeight(taskInfo)
+ if (y <= statusBarHeight && visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer)
+ visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
+ } else if (y > statusBarHeight && visualIndicator != null) {
+ visualIndicator?.releaseVisualIndicator()
+ visualIndicator = null
+ }
+ }
+ }
+
+ /**
+ * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ *
+ * @param taskInfo the task being dragged.
+ * @param y height of drag, to be checked against status bar height.
+ */
+ fun onDragPositioningEnd(
+ taskInfo: RunningTaskInfo,
+ y: Float
+ ) {
+ val statusBarHeight = getStatusBarHeight(taskInfo)
+ if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ visualIndicator?.releaseVisualIndicator()
+ visualIndicator = null
+ moveToFullscreenWithAnimation(taskInfo)
+ }
+ }
+
+ /**
+ * Perform checks required on drag move. Create/release fullscreen indicator and transitions
+ * indicator to freeform or fullscreen dimensions as needed.
+ *
+ * @param taskInfo the task being dragged.
+ * @param taskSurface SurfaceControl of dragged task.
+ * @param y coordinate of dragged task. Used for checks against status bar height.
+ */
+ fun onDragPositioningMoveThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ y: Float
+ ) {
+ if (visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer)
+ visualIndicator?.createFullscreenIndicator()
+ }
+ val indicator = visualIndicator ?: return
+ if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
+ if (indicator.isFullscreen) {
+ indicator.transitionFullscreenIndicatorToFreeform()
+ }
+ } else if (!indicator.isFullscreen) {
+ indicator.transitionFreeformIndicatorToFullscreen()
+ }
+ }
+
+ /**
+ * Perform checks required when drag ends under status bar area.
+ *
+ * @param taskInfo the task being dragged.
+ * @param y height of drag, to be checked against status bar height.
+ */
+ fun onDragPositioningEndThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ freeformBounds: Rect
+ ) {
+ moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ visualIndicator?.releaseVisualIndicator()
+ visualIndicator = null
+ }
+
+
+ private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
+ return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ }
+
+ /**
+ * Returns the threshold at which we transition a task into freeform when dragging a
+ * fullscreen task down from the status bar
+ */
+ private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
+ return 2 * getStatusBarHeight(taskInfo)
+ }
+
+
+ /**
* Adds a listener to find out about changes in the visibility of freeform tasks.
*
* @param listener the listener to add.
@@ -318,4 +536,18 @@ class DesktopTasksController(
return result[0]
}
}
+
+ companion object {
+ private val DESKTOP_DENSITY_OVERRIDE =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+ private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+ /**
+ * Check if desktop density override is enabled
+ */
+ @JvmStatic
+ fun isDesktopDensityOverrideSet(): Boolean {
+ return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
new file mode 100644
index 000000000000..27eda16f4171
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks
+ * entering and exiting freeform.
+ */
+public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+
+ private final Transitions mTransitions;
+ private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ // The size of the screen during drag relative to the fullscreen size
+ public static final float DRAG_FREEFORM_SCALE = 0.4f;
+ // The size of the screen after drag relative to the fullscreen size
+ public static final float FINAL_FREEFORM_SCALE = 0.6f;
+ public static final int FREEFORM_ANIMATION_DURATION = 336;
+
+ private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+ private Point mStartPosition;
+
+ public EnterDesktopTaskTransitionHandler(
+ Transitions transitions) {
+ this(transitions, SurfaceControl.Transaction::new);
+ }
+
+ public EnterDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Supplier<SurfaceControl.Transaction> supplier) {
+ mTransitions = transitions;
+ mTransactionSupplier = supplier;
+ }
+
+ /**
+ * Starts Transition of a given type
+ * @param type Transition type
+ * @param wct WindowContainerTransaction for transition
+ */
+ public void startTransition(@WindowManager.TransitionType int type,
+ @NonNull WindowContainerTransaction wct) {
+ final IBinder token = mTransitions.startTransition(type, wct, this);
+ mPendingTransitionTokens.add(token);
+ }
+
+ /**
+ * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param startPosition Position of task when transition is triggered
+ */
+ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
+ Point startPosition) {
+ mStartPosition = startPosition;
+ startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean transitionHandled = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
+ continue;
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
+ transitionHandled |= startChangeTransition(
+ transition, info.getType(), change, startT, finishCallback);
+ }
+ }
+
+ mPendingTransitionTokens.remove(transition);
+
+ return transitionHandled;
+ }
+
+ private boolean startChangeTransition(
+ @NonNull IBinder transition,
+ @WindowManager.TransitionType int type,
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (!mPendingTransitionTokens.contains(transition)) {
+ return false;
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (type == Transitions.TRANSIT_ENTER_FREEFORM
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
+ // to null and we don't require an animation
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, null);
+ startT.apply();
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ return true;
+ }
+
+ Rect endBounds = change.getEndAbsBounds();
+ if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && !endBounds.isEmpty()) {
+ // This Transition animates a task to freeform bounds after being dragged into freeform
+ // mode and brings the remaining freeform tasks to front
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, endBounds.width(),
+ endBounds.height());
+ startT.apply();
+
+ // We want to find the scale of the current bounds relative to the end bounds. The
+ // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
+ // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
+ // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final float animationValue = (float) animation.getAnimatedValue();
+ t.setScale(sc, animationValue, animationValue);
+
+ final float animationWidth = endBounds.width() * animationValue;
+ final float animationHeight = endBounds.height() * animationValue;
+ final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
+ final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
+
+ t.setPosition(sc, animationX, animationY);
+ t.apply();
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+
+ animator.start();
+ return true;
+ }
+
+ if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mStartPosition != null) {
+ // This Transition animates a task to fullscreen after being dragged from the status
+ // bar and then released back into the status bar area
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, null);
+ startT.apply();
+
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final float scale = animation.getAnimatedFraction();
+ t.setPosition(sc, mStartPosition.x * (1 - scale),
+ mStartPosition.y * (1 - scale));
+ t.setScale(sc, scale, scale);
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+}
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
new file mode 100644
index 000000000000..248a5fce6884
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+
+/**
+ * The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks
+ * entering and exiting freeform.
+ */
+public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+ private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+ private final Context mContext;
+ private final Transitions mTransitions;
+ private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+
+ private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ public ExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Context context) {
+ this(transitions, SurfaceControl.Transaction::new, context);
+ }
+
+ private ExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Supplier<SurfaceControl.Transaction> supplier,
+ Context context) {
+ mTransitions = transitions;
+ mTransactionSupplier = supplier;
+ mContext = context;
+ }
+
+ /**
+ * Starts Transition of a given type
+ * @param type Transition type
+ * @param wct WindowContainerTransaction for transition
+ */
+ public void startTransition(@WindowManager.TransitionType int type,
+ @NonNull WindowContainerTransaction wct) {
+ final IBinder token = mTransitions.startTransition(type, wct, this);
+ mPendingTransitionTokens.add(token);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean transitionHandled = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
+ continue;
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
+ transitionHandled |= startChangeTransition(
+ transition, info.getType(), change, startT, finishCallback);
+ }
+ }
+
+ mPendingTransitionTokens.remove(transition);
+
+ return transitionHandled;
+ }
+
+ @VisibleForTesting
+ boolean startChangeTransition(
+ @NonNull IBinder transition,
+ @WindowManager.TransitionType int type,
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (!mPendingTransitionTokens.contains(transition)) {
+ return false;
+ }
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (type == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ // This Transition animates a task to fullscreen after being dragged to status bar
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+ final SurfaceControl sc = change.getLeash();
+ startT.setCrop(sc, null);
+ startT.apply();
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(FULLSCREEN_ANIMATION_DURATION);
+ final Rect startBounds = change.getStartAbsBounds();
+ final float scaleX = (float) startBounds.width() / screenWidth;
+ final float scaleY = (float) startBounds.height() / screenHeight;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ Point startPos = new Point(startBounds.left,
+ startBounds.top);
+ animator.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ float currentScaleX = scaleX + ((1 - scaleX) * fraction);
+ float currentScaleY = scaleY + ((1 - scaleY) * fraction);
+ t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction));
+ t.setScale(sc, currentScaleX, currentScaleY);
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index 926cfb3b12ef..deb7c6db338f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module desktop owners
+atsjenk@google.com
madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 60e5ff27cab9..e1a56a1a5a7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,6 +112,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
onChangeTransitionReady(change, startT, finishT);
break;
}
+ mWindowDecorViewModel.onTransitionReady(transition, info, change);
}
mTransitionToTaskInfo.put(transition, taskInfoList);
}
@@ -168,6 +169,8 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
} else {
mTransitionToTaskInfo.put(playing, infoOfMerged);
}
+
+ mWindowDecorViewModel.onTransitionMerged(merged, playing);
}
@Override
@@ -175,7 +178,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
final List<ActivityManager.RunningTaskInfo> taskInfo =
mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
mTransitionToTaskInfo.remove(transition);
-
+ mWindowDecorViewModel.onTransitionFinished(transition);
for (int i = 0; i < taskInfo.size(); ++i) {
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 0c2d5c49f830..ccbb9cf298a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module freeform owners
+atsjenk@google.com
madym@google.com
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 d094c229e0f8..998728d65e6a 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
@@ -129,6 +129,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+ boolean oldVisible = state.mTaskInfo.isVisible;
if (mWindowDecorViewModelOptional.isPresent()) {
mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
@@ -138,12 +139,18 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
updateRecentsForVisibleFullscreenTask(taskInfo);
final Point positionInParent = state.mTaskInfo.positionInParent;
- if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+ boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
+ boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
+
+ if (becameVisible || positionInParentChanged) {
mSyncQueue.runInSync(t -> {
if (!state.mLeash.isValid()) {
// Task vanished before sync completion
return;
}
+ if (becameVisible) {
+ t.show(state.mLeash);
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 9796e4c29352..6d46a9c3d0e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -21,6 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -33,6 +36,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.view.Display;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -44,6 +48,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -80,6 +85,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
+ /**
+ * The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
+ * {@link Display#getRotation} values are mapped to screen orientations
+ */
+ private final boolean mReverseDefaultRotationEnabled;
+
@VisibleForTesting
ActivityManager.RunningTaskInfo mLaunchRootTask;
@VisibleForTesting
@@ -188,6 +199,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
public KidsModeTaskOrganizer(
@@ -211,6 +224,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
/**
@@ -267,6 +282,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mLaunchRootTask = taskInfo;
}
+ if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mHomeTask)) {
+ mHomeTask = taskInfo;
+ }
+
super.onTaskInfoChanged(taskInfo);
}
@@ -289,7 +309,17 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
// Needed since many Kids apps aren't optimised to support both orientations and it will be
// hard for kids to understand the app compat mode.
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
- setIsIgnoreOrientationRequestDisabled(true);
+ if (mReverseDefaultRotationEnabled) {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */
+ new int[]{SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ /* toOrientations */
+ new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE});
+ } else {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */ null, /* toOrientations */ null);
+ }
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
if (displayLayout != null) {
mDisplayWidth = displayLayout.width();
@@ -310,7 +340,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
void disable() {
- setIsIgnoreOrientationRequestDisabled(false);
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
+ /* fromOrientations */ null, /* toOrientations */ null);
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
mOnInsetsChangedListener);
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
@@ -376,6 +407,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
final WindowContainerTransaction wct = getWindowContainerTransaction();
final Rect taskBounds = calculateBounds();
wct.setBounds(mLaunchRootTask.token, taskBounds);
+ wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mSyncQueue.queue(wct);
final SurfaceControl finalLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 5b9f0c41e31e..4ec1351aa22b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.onehanded;
import static android.view.Display.DEFAULT_DISPLAY;
import android.graphics.Rect;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.Looper;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -129,7 +129,7 @@ public class OneHandedTouchHandler implements OneHandedTransitionCallback {
private void updateIsEnabled() {
disposeInputChannel();
if (mIsEnabled) {
- mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ mInputMonitor = InputManagerGlobal.getInstance().monitorGestureInput(
"onehanded-touch", DEFAULT_DISPLAY);
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 2624ee536b58..78de5f3e7a1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -70,4 +70,14 @@ interface IPip {
* Sets the next pip animation type to be the alpha animation.
*/
oneway void setPipAnimationTypeToAlpha() = 5;
+
+ /**
+ * Sets the height and visibility of the Launcher keep clear area.
+ */
+ oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
+
+ /**
+ * Sets the app icon size in pixel used by Launcher
+ */
+ oneway void setLauncherAppIconSize(int iconSizePx) = 7;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
index afddfab99a2b..ec09827fa4d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module pip owner
hwwang@google.com
+mateuszc@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 23f73f614294..1d7e64988359 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
@@ -30,13 +30,17 @@ import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.view.Surface;
import android.view.SurfaceControl;
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.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -60,6 +64,14 @@ public class PipAnimationController {
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
+ /**
+ * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+ * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+ * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+ * animation style to an unrelated task.
+ */
+ private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
public static final int TRANSITION_DIRECTION_NONE = 0;
public static final int TRANSITION_DIRECTION_SAME = 1;
public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -108,6 +120,9 @@ public class PipAnimationController {
});
private PipTransitionAnimator mCurrentAnimator;
+ @AnimationType
+ private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private long mLastOneShotAlphaAnimationTime;
public PipAnimationController(PipSurfaceTransactionHelper helper) {
mSurfaceTransactionHelper = helper;
@@ -187,6 +202,11 @@ public class PipAnimationController {
return mCurrentAnimator;
}
+ /** Reset animator state to prevent it from being used after its lifetime. */
+ public void resetAnimatorState() {
+ mCurrentAnimator = null;
+ }
+
private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -209,13 +229,44 @@ public class PipAnimationController {
/**
* Quietly cancel the animator by removing the listeners first.
*/
- public static void quietCancel(@NonNull ValueAnimator animator) {
+ static void quietCancel(@NonNull ValueAnimator animator) {
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animator.cancel();
}
/**
+ * Sets the preferred enter animation type for one time. This is typically used to set the
+ * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+ * <p>
+ * For example, gesture navigation would first fade out the PiP activity, and the transition
+ * should be responsible to animate in (such as fade in) the PiP.
+ */
+ public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+ mOneShotAnimationType = animationType;
+ if (animationType == ANIM_TYPE_ALPHA) {
+ mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+ }
+ }
+
+ /** Returns the preferred animation type and consumes the one-shot type if needed. */
+ @AnimationType
+ public int takeOneShotEnterAnimationType() {
+ final int type = mOneShotAnimationType;
+ if (type == ANIM_TYPE_ALPHA) {
+ // Restore to default type.
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+ > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Alpha animation is expired. Use bounds animation.");
+ return ANIM_TYPE_BOUNDS;
+ }
+ }
+ return type;
+ }
+
+ /**
* Additional callback interface for PiP animation
*/
public static class PipAnimationCallback {
@@ -249,7 +300,7 @@ public class PipAnimationController {
* @return true if handled by the handler, false otherwise.
*/
public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
return false;
}
}
@@ -350,9 +401,10 @@ public class PipAnimationController {
}
boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
if (mPipTransactionHandler != null) {
- return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
+ return mPipTransactionHandler.handlePipTransaction(
+ leash, tx, destinationBounds, alpha);
}
return false;
}
@@ -370,9 +422,11 @@ public class PipAnimationController {
new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
}
- void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
+ void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
+ int appIconSizePx) {
reattachContentOverlay(
- new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo));
+ new PipContentOverlay.PipAppIconOverlay(context, bounds,
+ new IconProvider(context).getIcon(activityInfo), appIconSizePx));
}
private void reattachContentOverlay(PipContentOverlay overlay) {
@@ -495,7 +549,9 @@ public class PipAnimationController {
getSurfaceTransactionHelper().alpha(tx, leash, alpha)
.round(tx, leash, shouldApplyCornerRadius())
.shadow(tx, leash, shouldApplyShadowRadius());
- tx.apply();
+ if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) {
+ tx.apply();
+ }
}
@Override
@@ -610,7 +666,7 @@ public class PipAnimationController {
.shadow(tx, leash, shouldApplyShadowRadius());
}
}
- if (!handlePipTransaction(leash, tx, bounds)) {
+ if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) {
tx.apply();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 867162be4c6d..24d0b996a3cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -198,7 +198,7 @@ public class PipBoundsAlgorithm {
/**
* @return whether the given {@param aspectRatio} is valid.
*/
- private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
&& Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 5be18d852990..9a775dff1f69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -30,7 +30,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Size;
-import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -44,7 +43,9 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -77,6 +78,7 @@ public class PipBoundsState {
private final @NonNull Rect mExpandedBounds = new Rect();
private final @NonNull Rect mNormalMovementBounds = new Rect();
private final @NonNull Rect mExpandedMovementBounds = new Rect();
+ private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState;
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final @NonNull Context mContext;
@@ -84,10 +86,9 @@ public class PipBoundsState {
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
+ private final LauncherState mLauncherState = new LauncherState();
private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
- private int mDisplayId = Display.DEFAULT_DISPLAY;
- private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
@@ -115,15 +116,23 @@ public class PipBoundsState {
* @see android.view.View#setPreferKeepClearRects
*/
private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+ /**
+ * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+ * as unrestricted keep clear area. Values in this map would be appended to
+ * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+ */
+ private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
+ public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
}
/** Reloads the resources. */
@@ -290,31 +299,16 @@ public class PipBoundsState {
return mLastPipComponentName;
}
- /** Get the current display id. */
- public int getDisplayId() {
- return mDisplayId;
- }
-
- /** Set the current display id for the associated display layout. */
- public void setDisplayId(int displayId) {
- mDisplayId = displayId;
- }
-
/** Returns the display's bounds. */
@NonNull
public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ return mPipDisplayLayoutState.getDisplayBounds();
}
/** Get a copy of the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
- return new DisplayLayout(mDisplayLayout);
+ return mPipDisplayLayoutState.getDisplayLayout();
}
@VisibleForTesting
@@ -393,6 +387,16 @@ public class PipBoundsState {
mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
}
+ /** Add a named unrestricted keep clear area. */
+ public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+ mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+ }
+
+ /** Remove a named unrestricted keep clear area. */
+ public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+ mNamedUnrestrictedKeepClearAreas.remove(name);
+ }
+
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
@@ -400,7 +404,10 @@ public class PipBoundsState {
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
- return mUnrestrictedKeepClearAreas;
+ if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+ final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+ unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+ return unrestrictedAreas;
}
/**
@@ -476,6 +483,10 @@ public class PipBoundsState {
mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
}
+ public LauncherState getLauncherState() {
+ return mLauncherState;
+ }
+
/** Source of truth for the current bounds of PIP that may be in motion. */
public static class MotionBoundsState {
/** The bounds used when PIP is in motion (e.g. during a drag or animation) */
@@ -528,6 +539,25 @@ public class PipBoundsState {
}
}
+ /** Data class for Launcher state. */
+ public static final class LauncherState {
+ private int mAppIconSizePx;
+
+ public void setAppIconSizePx(int appIconSizePx) {
+ mAppIconSizePx = appIconSizePx;
+ }
+
+ public int getAppIconSizePx() {
+ return mAppIconSizePx;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + LauncherState.class.getSimpleName());
+ pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx());
+ }
+ }
+
static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
@@ -568,7 +598,6 @@ public class PipBoundsState {
pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
- pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
@@ -582,6 +611,7 @@ public class PipBoundsState {
} else {
mPipReentryState.dump(pw, innerPrefix);
}
+ mLauncherState.dump(pw, innerPrefix);
mMotionBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 480bf93b2ddb..9fa57cacb11f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -20,9 +20,6 @@ import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -39,6 +36,9 @@ import android.window.TaskSnapshot;
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
+ // Fixed string used in WMShellFlickerTests
+ protected static final String LAYER_NAME = "PipContentOverlay";
+
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
@@ -86,7 +86,7 @@ public abstract class PipContentOverlay {
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.setColorLayer()
.build();
}
@@ -139,7 +139,7 @@ public abstract class PipContentOverlay {
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -174,7 +174,8 @@ public abstract class PipContentOverlay {
/** A {@link PipContentOverlay} shows app icon on solid color background. */
public static final class PipAppIconOverlay extends PipContentOverlay {
private static final String TAG = PipAppIconOverlay.class.getSimpleName();
- private static final int APP_ICON_SIZE_DP = 48;
+ // The maximum size for app icon in pixel.
+ private static final int MAX_APP_ICON_SIZE_DP = 72;
private final Context mContext;
private final int mAppIconSizePx;
@@ -184,17 +185,19 @@ public abstract class PipContentOverlay {
private Bitmap mBitmap;
- public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) {
+ public PipAppIconOverlay(Context context, Rect appBounds,
+ Drawable appIcon, int appIconSizePx) {
mContext = context;
- mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
- context.getResources().getDisplayMetrics());
+ final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
+ mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
mAppBounds = new Rect(appBounds);
mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
Bitmap.Config.ARGB_8888);
- prepareAppIconOverlay(activityInfo);
+ prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -234,7 +237,7 @@ public abstract class PipContentOverlay {
}
}
- private void prepareAppIconOverlay(ActivityInfo activityInfo) {
+ private void prepareAppIconOverlay(Drawable appIcon) {
final Canvas canvas = new Canvas();
canvas.setBitmap(mBitmap);
final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
@@ -248,8 +251,6 @@ public abstract class PipContentOverlay {
} finally {
ta.recycle();
}
- final Drawable appIcon = loadActivityInfoIcon(activityInfo,
- mContext.getResources().getConfiguration().densityDpi);
final Rect appIconBounds = new Rect(
mAppBounds.centerX() - mAppIconSizePx / 2,
mAppBounds.centerY() - mAppIconSizePx / 2,
@@ -259,24 +260,5 @@ public abstract class PipContentOverlay {
appIcon.draw(canvas);
mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
}
-
- // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon
- private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
- final int iconRes = ai.getIconResource();
- Drawable icon = null;
- // Get the preferred density icon from the app's resources
- if (density != 0 && iconRes != 0) {
- try {
- final Resources resources = mContext.getPackageManager()
- .getResourcesForApplication(ai.applicationInfo);
- icon = resources.getDrawableForDensity(iconRes, density);
- } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { }
- }
- // Get the default density icon
- if (icon == null) {
- icon = ai.loadIcon(mContext.getPackageManager());
- }
- return icon;
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
new file mode 100644
index 000000000000..0f76af48199f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -0,0 +1,91 @@
+/*
+ * 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.pip;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Acts as a source of truth for display related information for PIP.
+ */
+@WMSingleton
+public class PipDisplayLayoutState {
+ private static final String TAG = PipDisplayLayoutState.class.getSimpleName();
+
+ private Context mContext;
+ private int mDisplayId;
+ @NonNull private DisplayLayout mDisplayLayout;
+
+ @Inject
+ public PipDisplayLayoutState(Context context) {
+ mContext = context;
+ mDisplayLayout = new DisplayLayout();
+ }
+
+ /** Update the display layout. */
+ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ /** Get a copy of the display layout. */
+ @NonNull
+ public DisplayLayout getDisplayLayout() {
+ return new DisplayLayout(mDisplayLayout);
+ }
+
+ /** Get the display bounds */
+ @NonNull
+ public Rect getDisplayBounds() {
+ return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ }
+
+ /**
+ * Apply a rotation to this layout and its parameters.
+ * @param targetRotation
+ */
+ public void rotateTo(@Surface.Rotation int targetRotation) {
+ mDisplayLayout.rotateTo(mContext.getResources(), targetRotation);
+ }
+
+ /** Get the current display id */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** Set the current display id for the associated display layout. */
+ public void setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 000624499f79..0775f5279e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -45,6 +45,13 @@ public interface PipMenuController {
String MENU_WINDOW_TITLE = "PipMenuView";
/**
+ * Used with
+ * {@link PipMenuController#movePipMenu(SurfaceControl, SurfaceControl.Transaction, Rect,
+ * float)} to indicate that we don't want to affect the alpha value of the menu surfaces.
+ */
+ float ALPHA_NO_CHANGE = -1f;
+
+ /**
* Called when
* {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
* is called.
@@ -85,8 +92,8 @@ public interface PipMenuController {
* need to synchronize the movements on the same frame as PiP.
*/
default void movePipMenu(@Nullable SurfaceControl pipLeash,
- @Nullable SurfaceControl.Transaction t,
- Rect destinationBounds) {}
+ @Nullable SurfaceControl.Transaction t, Rect destinationBounds, float alpha) {
+ }
/**
* Update the PiP menu with the given bounds for re-layout purposes.
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 f11836ea5bee..b6216b340b38 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -61,7 +62,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
@@ -72,6 +72,7 @@ import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -79,13 +80,11 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -111,12 +110,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
- /**
- * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
- * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
- * navigation, then the alpha type is unexpected.
- */
- private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -128,7 +121,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
@@ -143,13 +136,36 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
+ // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
+ private Runnable mPipFinishResizeWCTRunnable;
+
+ private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
+ new WindowContainerTransactionCallback() {
+ @Override
+ public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+ t.apply();
+
+ // execute the runnable if non-null after WCT is applied to finish resizing pip
+ maybePerformFinishResizeCallback();
+ }
+ };
+
+ private void maybePerformFinishResizeCallback() {
+ if (mPipFinishResizeWCTRunnable != null) {
+ mPipFinishResizeWCTRunnable.run();
+ mPipFinishResizeWCTRunnable = null;
+ }
+ }
+
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
+ private boolean mIsCancelled;
@Override
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = false;
sendOnPipTransitionStarted(direction);
}
@@ -157,6 +173,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ if (mIsCancelled) {
+ sendOnPipTransitionFinished(direction);
+ return;
+ }
final int animationType = animator.getAnimationType();
final Rect destinationBounds = animator.getDestinationBounds();
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
@@ -195,6 +215,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = true;
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
animator::clearContentOverlay, true /* withStartDelay */);
@@ -232,7 +253,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}, null);
}
- private boolean shouldSyncPipTransactionWithMenu() {
+ protected boolean shouldSyncPipTransactionWithMenu() {
return mPipMenuController.isMenuVisible();
}
@@ -260,9 +281,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
new PipAnimationController.PipTransactionHandler() {
@Override
public boolean handlePipTransaction(SurfaceControl leash,
- SurfaceControl.Transaction tx, Rect destinationBounds) {
+ SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
if (shouldSyncPipTransactionWithMenu()) {
- mPipMenuController.movePipMenu(leash, tx, destinationBounds);
+ mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha);
return true;
}
return false;
@@ -277,8 +298,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private WindowContainerToken mToken;
private SurfaceControl mLeash;
protected PipTransitionState mPipTransitionState;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- private long mLastOneShotAlphaAnimationTime;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
protected PictureInPictureParams mPictureInPictureParams;
@@ -316,7 +335,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -332,7 +351,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -366,6 +385,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mPipTransitionController;
}
+ PipAnimationController.PipTransactionHandler getPipTransactionHandler() {
+ return mPipTransactionHandler;
+ }
+
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
@@ -398,15 +421,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
+ * Override if the PiP should always use a fade-in animation during PiP entry.
+ *
+ * @return true if the mOneShotAnimationType should always be
* {@link PipAnimationController#ANIM_TYPE_ALPHA}.
*/
- public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- if (animationType == ANIM_TYPE_ALPHA) {
- mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
- }
+ protected boolean shouldAlwaysFadeIn() {
+ return false;
+ }
+
+ /**
+ * Whether the menu should get attached as early as possible when entering PiP.
+ *
+ * @return whether the menu should be attached before
+ * {@link PipBoundsAlgorithm#getEntryDestinationBounds()} is called.
+ */
+ protected boolean shouldAttachMenuEarly() {
+ return false;
}
/**
@@ -538,6 +569,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
+ if (mSplitScreenOptional.isPresent()) {
+ // If pip activity will reparent to origin task case and if the origin task still under
+ // split root, just exit split screen here to ensure it could expand to fullscreen.
+ SplitScreenController split = mSplitScreenOptional.get();
+ if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+ split.exitSplitScreen(INVALID_TASK_ID,
+ SplitScreenController.EXIT_REASON_APP_FINISHED);
+ }
+ }
mSyncTransactionQueue.queue(wct);
mSyncTransactionQueue.runInSync(t -> {
// Make sure to grab the latest source hint rect as it could have been
@@ -583,11 +623,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Removes PiP immediately.
*/
public void removePip() {
- if (!mPipTransitionState.isInPip() || mToken == null) {
+ if (!mPipTransitionState.isInPip() || mToken == null || mLeash == null) {
ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not allowed to removePip in current state"
- + " mState=%d mToken=%s", TAG, mPipTransitionState.getTransitionState(),
- mToken);
+ + " mState=%d mToken=%s mLeash=%s", TAG,
+ mPipTransitionState.getTransitionState(), mToken, mLeash);
return;
}
@@ -653,7 +693,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
- if (info.displayId != mPipBoundsState.getDisplayId()
+ if (info.displayId != mPipDisplayLayoutState.getDisplayId()
&& mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(info.displayId);
}
@@ -680,47 +720,47 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA
- && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
- > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Alpha animation is expired. Use bounds animation.", TAG);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- }
-
+ final int animationType = shouldAlwaysFadeIn()
+ ? ANIM_TYPE_ALPHA
+ : mPipAnimationController.takeOneShotEnterAnimationType();
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mPipTransitionController.setEnterAnimationType(animationType);
// For Shell transition, we will animate the window in PipTransition#startAnimation
// instead of #onTaskAppeared.
return;
}
if (mWaitForFixedRotation) {
- onTaskAppearedWithFixedRotation();
+ onTaskAppearedWithFixedRotation(animationType);
return;
}
+ if (shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- mPipMenuController.attach(mLeash);
+ if (animationType == ANIM_TYPE_BOUNDS) {
+ if (!shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
info.pictureInPictureParams, currentBounds);
scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
null /* updateBoundsCallback */);
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (animationType == ANIM_TYPE_ALPHA) {
enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + animationType);
}
}
- private void onTaskAppearedWithFixedRotation() {
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ private void onTaskAppearedWithFixedRotation(int animationType) {
+ if (animationType == ANIM_TYPE_ALPHA) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
// If deferred, hside the surface till fixed rotation is completed.
@@ -729,7 +769,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
tx.setAlpha(mLeash, 0f);
tx.show(mLeash);
tx.apply();
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
return;
}
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -819,7 +858,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Nullable SurfaceControl.Transaction boundsChangeTransaction) {
// PiP menu is attached late in the process here to avoid any artifacts on the leash
// caused by addShellRoot when in gesture navigation mode.
- mPipMenuController.attach(mLeash);
+ if (!shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
@@ -970,6 +1011,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mPipTransitionController.onFixedRotationFinished();
clearWaitForFixedRotation();
return;
}
@@ -1161,21 +1203,32 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
if (newDestinationBounds.equals(currentDestinationBounds)) return;
- if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- if (mWaitForFixedRotation) {
- // The new destination bounds are in next rotation (DisplayLayout has been rotated
- // in computeRotatedBounds). The animation runs in previous rotation so the end
- // bounds need to be transformed.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
- final Rect rotatedEndBounds = new Rect(newDestinationBounds);
- rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
- animator.updateEndValue(rotatedEndBounds);
- } else {
- animator.updateEndValue(newDestinationBounds);
+ updateAnimatorBounds(newDestinationBounds);
+ destinationBoundsOut.set(newDestinationBounds);
+ }
+
+ /**
+ * Directly update the animator bounds.
+ */
+ public void updateAnimatorBounds(Rect bounds) {
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
+ if (mWaitForFixedRotation) {
+ // The new destination bounds are in next rotation (DisplayLayout has been
+ // rotated in computeRotatedBounds). The animation runs in previous rotation so
+ // the end bounds need to be transformed.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect rotatedEndBounds = new Rect(bounds);
+ rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+ animator.updateEndValue(rotatedEndBounds);
+ } else {
+ animator.updateEndValue(bounds);
+ }
}
+ animator.setDestinationBounds(bounds);
}
- animator.setDestinationBounds(newDestinationBounds);
- destinationBoundsOut.set(newDestinationBounds);
}
/**
@@ -1184,7 +1237,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
mPictureInPictureParams.getAspectRatioFloat())) {
- mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(
+ params.getAspectRatioFloat())) {
+ mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ } else {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: New aspect ratio is not valid."
+ + " hasAspectRatio=%b"
+ + " aspectRatio=%f",
+ TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat());
+ }
}
if (mDeferredTaskInfo != null
|| PipUtils.remoteActionsChanged(params.getActions(),
@@ -1223,8 +1285,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/**
* Animates resizing of the pinned stack given the duration and start bounds.
* This is used when the starting bounds is not the current PiP bounds.
+ *
+ * @param pipFinishResizeWCTRunnable callback to run after window updates are complete
*/
public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+ float startingAngle, Consumer<Rect> updateBoundsCallback,
+ Runnable pipFinishResizeWCTRunnable) {
+ mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable;
+ if (mPipFinishResizeWCTRunnable != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "mPipFinishResizeWCTRunnable is set to be called once window updates");
+ }
+
+ scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle,
+ updateBoundsCallback);
+ }
+
+ private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
float startingAngle, Consumer<Rect> updateBoundsCallback) {
if (mWaitForFixedRotation) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1318,7 +1395,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
if (shouldSyncPipTransactionWithMenu()) {
- mPipMenuController.movePipMenu(mLeash, tx, toBounds);
+ mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE);
} else {
tx.apply();
}
@@ -1455,6 +1532,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (snapshotSurface != null) {
mSyncTransactionQueue.queue(wct);
mSyncTransactionQueue.runInSync(t -> {
+ // reset the pinch gesture
+ maybePerformFinishResizeCallback();
+
// Scale the snapshot from its pre-resize bounds to the post-resize bounds.
mSurfaceTransactionHelper.scale(t, snapshotSurface, preResizeBounds,
snapshotDest);
@@ -1467,9 +1547,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
applyFinishBoundsResize(wct, direction, false);
}
} else {
- final boolean isPipTopLeft =
- direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
- applyFinishBoundsResize(wct, direction, isPipTopLeft);
+ applyFinishBoundsResize(wct, direction, isPipToTopLeft());
+ // Use sync transaction to apply finish transaction for enter split case.
+ if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ mSyncTransactionQueue.runInSync(t -> {
+ t.merge(tx);
+ });
+ }
}
finishResizeForMenu(destinationBounds);
@@ -1480,7 +1564,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!isInPip()) {
return;
}
- mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
mPipMenuController.updateMenuBounds(destinationBounds);
}
@@ -1488,6 +1573,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@PipAnimationController.TransitionDirection int direction,
SurfaceControl.Transaction tx,
WindowContainerTransaction wct) {
+ if (mLeash == null || !mLeash.isValid()) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid leash on prepareFinishResizeTransaction: %s", TAG, mLeash);
+ return;
+ }
final Rect taskBounds;
if (isInPipDirection(direction)) {
// If we are animating from fullscreen using a bounds animation, then reset the
@@ -1506,7 +1596,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
wct.setBounds(mToken, taskBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
+ // Pip to split should use sync transaction to sync split bounds change.
+ if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ wct.setBoundsChangeTransaction(mToken, tx);
+ }
}
/**
@@ -1521,8 +1614,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
mSplitScreenOptional.ifPresent(splitScreenController ->
splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
- } else {
+ } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+ // when leaving PiP we can call the callback without sync
+ maybePerformFinishResizeCallback();
mTaskOrganizer.applyTransaction(wct);
+ } else {
+ mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
}
}
@@ -1594,9 +1691,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, mTaskInfo.topActivityInfo);
+ mContext, currentBounds, mTaskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
animator.setColorContentOverlay(mContext);
}
@@ -1621,15 +1719,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return animator;
}
- /** Computes destination bounds in old rotation and returns source hint rect if available. */
+ /** Computes destination bounds in old rotation and returns source hint rect if available.
+ *
+ * Note: updates the internal state of {@link PipDisplayLayoutState} by applying a rotation
+ * transformation onto the display layout.
+ */
private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
Rect outDestinationBounds, Rect sourceHintRect) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(mNextRotation);
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
@@ -1756,6 +1854,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator::clearContentOverlay);
}
PipAnimationController.quietCancel(animator);
+ mPipAnimationController.resetAnimatorState();
}
}
@@ -1782,7 +1881,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
+ " binder=" + (mToken != null ? mToken.asBinder() : null));
pw.println(innerPrefix + "mLeash=" + mLeash);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
- pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
}
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 6b0337d3fb4a..99cb6f782d01 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
@@ -64,8 +64,6 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -85,11 +83,11 @@ public class PipTransition extends PipTransitionController {
private final Context mContext;
private final PipTransitionState mPipTransitionState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
private final Rect mExitDestinationBounds = new Rect();
@@ -116,7 +114,7 @@ public class PipTransition extends PipTransitionController {
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -127,7 +125,7 @@ public class PipTransition extends PipTransitionController {
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -135,20 +133,6 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void setIsFullAnimation(boolean isFullAnimation) {
- setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
- }
-
- /**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- }
-
- @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
@@ -274,6 +258,8 @@ public class PipTransition extends PipTransitionController {
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
if (requestHasPipEnter(request)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: handle PiP enter request", TAG);
WindowContainerTransaction wct = new WindowContainerTransaction();
augmentRequest(transition, request, wct);
return wct;
@@ -288,7 +274,7 @@ public class PipTransition extends PipTransitionController {
if (!requestHasPipEnter(request)) {
throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
mRequestedEnterTransition = transition;
mRequestedEnterTask = request.getTriggerTask().token;
outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,16 +294,12 @@ public class PipTransition extends PipTransitionController {
@Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
- if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
// A fade-in was requested but not-yet started. In this case, just recalculate the
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
if (rotationDelta != Surface.ROTATION_0) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
wct.setBounds(mRequestedEnterTask, destinationBounds);
@@ -398,7 +380,7 @@ public class PipTransition extends PipTransitionController {
// Launcher may update the Shelf height during the animation, which will update the
// destination bounds. Because this is in fixed rotation, We need to make sure the
// finishTransaction is using the updated bounds in the display rotation.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
final Rect finishBounds = new Rect(destinationBounds);
rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
@@ -427,9 +409,31 @@ public class PipTransition extends PipTransitionController {
@Override
public void onFixedRotationStarted() {
+ fadeEnteredPipIfNeed(false /* show */);
+ }
+
+ @Override
+ public void onFixedRotationFinished() {
+ fadeEnteredPipIfNeed(true /* show */);
+ }
+
+ private void fadeEnteredPipIfNeed(boolean show) {
// The transition with this fixed rotation may be handled by other handler before reaching
// PipTransition, so we cannot do this in #startAnimation.
- if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) {
+ if (!mPipTransitionState.hasEnteredPip()) {
+ return;
+ }
+ if (show && mHasFadeOut) {
+ // If there is a pending transition, then let startAnimation handle it. And if it is
+ // handled, mHasFadeOut will be set to false and this runnable will be no-op. Otherwise
+ // make sure the PiP will reshow, e.g. swipe-up with fixed rotation (fade-out) but
+ // return to the current app (only finish the recent transition).
+ mTransitions.runOnIdle(() -> {
+ if (mHasFadeOut && mPipTransitionState.hasEnteredPip()) {
+ fadeExistingPip(true /* show */);
+ }
+ });
+ } else if (!show && !mHasFadeOut) {
// Fade out the existing PiP to avoid jump cut during seamless rotation.
fadeExistingPip(false /* show */);
}
@@ -495,10 +499,11 @@ public class PipTransition extends PipTransitionController {
// Reparent the pip leash to the root with max layer so that we can animate it outside of
// parent crop, and make sure it is not covered by other windows.
final SurfaceControl pipLeash = pipChange.getLeash();
- startTransaction.reparent(pipLeash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+ startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
// Note: because of this, the bounds to animate should be translated to the root coordinate.
- final Point offset = info.getRootOffset();
+ final Point offset = info.getRoot(rootIdx).getOffset();
final Rect currentBounds = mPipBoundsState.getBounds();
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
@@ -628,6 +633,7 @@ public class PipTransition extends PipTransitionController {
0 /* startingAngle */, rotationDelta);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
+ .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler())
.setDuration(mEnterExitAnimationDuration)
.start();
}
@@ -640,7 +646,7 @@ public class PipTransition extends PipTransitionController {
@NonNull TaskInfo taskInfo) {
startTransaction.apply();
finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
- mPipBoundsState.getDisplayBounds());
+ mPipDisplayLayoutState.getDisplayBounds());
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(null, null);
}
@@ -679,6 +685,11 @@ public class PipTransition extends PipTransitionController {
return false;
}
+ @Override
+ public void setEnterAnimationType(@PipAnimationController.AnimationType int type) {
+ mEnterAnimationType = type;
+ }
+
private void startEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -736,6 +747,11 @@ public class PipTransition extends PipTransitionController {
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
+
+ if (mPipOrganizer.shouldAttachMenuEarly()) {
+ mPipMenuController.attach(leash);
+ }
+
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -750,12 +766,14 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
- mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
+ if (!mPipOrganizer.shouldAttachMenuEarly()) {
+ mTransitions.getMainExecutor().executeDelayed(
+ () -> mPipMenuController.attach(leash), 0);
+ }
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
.setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -790,36 +808,45 @@ public class PipTransition extends PipTransitionController {
tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+
+ final int enterAnimationType = mEnterAnimationType;
+ if (enterAnimationType == ANIM_TYPE_ALPHA) {
startTransaction.setAlpha(leash, 0f);
}
startTransaction.apply();
PipAnimationController.PipTransitionAnimator animator;
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ if (enterAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
0 /* startingAngle */, rotationDelta);
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
+ // TODO(b/272819817): cleanup the null-check and extra logging.
+ final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
+ if (!hasTopActivityInfo) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
+ }
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)
+ && hasTopActivityInfo) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, taskInfo.topActivityInfo);
+ mContext, currentBounds, taskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
animator.setColorContentOverlay(mContext);
}
}
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: "
- + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler())
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
@@ -834,13 +861,9 @@ public class PipTransition extends PipTransitionController {
/** Computes destination bounds in old rotation and updates source hint rect if available. */
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
@@ -883,7 +906,7 @@ public class PipTransition extends PipTransitionController {
.setWindowCrop(leash, endBounds.width(), endBounds.height());
}
}
- mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+ mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
startTransaction.apply();
mPipOrganizer.onExitPipFinished(taskInfo);
@@ -950,7 +973,8 @@ public class PipTransition extends PipTransitionController {
}
private void finishResizeForMenu(Rect destinationBounds) {
- mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
mPipMenuController.updateMenuBounds(destinationBounds);
}
}
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 f51e247fe112..949d6f558c32 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
@@ -105,15 +105,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
- * Called to inform the transition that the animation should start with the assumption that
- * PiP is not animating from its original bounds, but rather a continuation of another
- * animation. For example, gesture navigation would first fade out the PiP activity, and the
- * transition should be responsible to animate in (such as fade in) the PiP.
- */
- public void setIsFullAnimation(boolean isFullAnimation) {
- }
-
- /**
* Called when the Shell wants to start an exit Pip transition/animation.
*/
public void startExitTransition(int type, WindowContainerTransaction out,
@@ -132,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
public void onFixedRotationStarted() {
}
+ /** Called when the fixed rotation finished. */
+ public void onFixedRotationFinished() {
+ }
+
public PipTransitionController(
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -234,6 +229,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
throw new IllegalStateException("Request isn't entering PiP");
}
+ /** Sets the type of animation when a PiP task appears. */
+ public void setEnterAnimationType(@PipAnimationController.AnimationType int type) {
+ }
+
/** Play a transition animation for entering PiP on a specific PiP change. */
public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
@NonNull final SurfaceControl.Transaction startTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index c6b5ce93fd35..db6138a0891f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -93,6 +93,11 @@ public class PipTransitionState {
return hasEnteredPip(mState);
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public boolean isEnteringPip() {
+ return isEnteringPip(mState);
+ }
+
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -130,6 +135,11 @@ public class PipTransitionState {
return state == ENTERED_PIP;
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public static boolean isEnteringPip(@TransitionState int state) {
+ return state == ENTERING_PIP;
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
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 94e593b106a5..e7a1395f541c 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
@@ -298,7 +298,8 @@ public class PhonePipMenuController implements PipMenuController {
}
// Sync the menu bounds before showing it in case it is out of sync.
- movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds);
+ movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
updateMenuBounds(stackBounds);
mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
@@ -311,7 +312,7 @@ public class PhonePipMenuController implements PipMenuController {
@Override
public void movePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
if (destinationBounds.isEmpty()) {
return;
}
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 a1483a8dedae..b0bb14b49db6 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
@@ -43,6 +43,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -71,6 +72,7 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -83,6 +85,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -115,11 +118,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
UserChangeListener {
private static final String TAG = "PipController";
+ private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
@VisibleForTesting
void setEnablePipKeepClearAlgorithm(boolean value) {
@@ -138,12 +143,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private DisplayInsetsController mDisplayInsetsController;
+ private TabletopModeController mTabletopModeController;
private Optional<OneHandedController> mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
@@ -156,6 +163,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
this::onKeepClearAreasChangedCallback;
private void onKeepClearAreasChangedCallback() {
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
if (!mEnablePipKeepClearAlgorithm) {
// early bail out if the keep clear areas feature is disabled
return;
@@ -181,14 +192,24 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the keep clear areas feature is disabled
return;
}
- // only move if already in pip, other transitions account for keep clear areas
- if (mPipTransitionState.hasEnteredPip()) {
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
+ // only move if we're in PiP or transitioning into PiP
+ if (!mPipTransitionState.shouldBlockResizeRequest()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
mPipBoundsAlgorithm);
// only move if the bounds are actually different
- if (destBounds != mPipBoundsState.getBounds()) {
- mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
- mEnterAnimationDuration, null);
+ if (!destBounds.equals(mPipBoundsState.getBounds())) {
+ if (mPipTransitionState.hasEnteredPip()) {
+ // if already in PiP, schedule separate animation
+ mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+ mEnterAnimationDuration, null);
+ } else if (mPipTransitionState.isEnteringPip()) {
+ // while entering PiP we just need to update animator bounds
+ mPipTaskOrganizer.updateAnimatorBounds(destBounds);
+ }
}
}
}
@@ -303,7 +324,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayAdded(int displayId) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -312,7 +333,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -322,7 +343,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
- if (mPipBoundsState.getDisplayId() == displayId) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
if (mEnablePipKeepClearAlgorithm) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
@@ -382,6 +403,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -393,6 +415,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -404,10 +427,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
+ pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor)
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
.mImpl;
}
@@ -422,6 +445,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -433,6 +457,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController tabletopModeController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
@@ -448,6 +473,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState;
@@ -465,6 +491,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
mDisplayInsetsController = displayInsetsController;
+ mTabletopModeController = tabletopModeController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -475,7 +502,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
- mPipBoundsState.setDisplayId(displayId);
+ mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
false /* saveRestoreSnapFraction */);
});
@@ -515,11 +542,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
- mPipBoundsState.setDisplayId(mContext.getDisplayId());
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -614,21 +640,23 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
- mDisplayInsetsController.addInsetsChangedListener(mPipBoundsState.getDisplayId(),
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
- DisplayLayout pendingLayout =
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ DisplayLayout pendingLayout = mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
if (mIsInFixedRotation
+ || mIsKeyguardShowingOrAnimating
|| pendingLayout.rotation()
!= mPipBoundsState.getDisplayLayout().rotation()) {
- // bail out if there is a pending rotation or fixed rotation change
+ // bail out if there is a pending rotation or fixed rotation change or
+ // there's a keyguard present
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- onDisplayChangedUncheck(
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
+ onDisplayChangedUncheck(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
if (!mEnablePipKeepClearAlgorithm) {
@@ -648,6 +676,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
+ mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+ if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+ final String tag = "tabletop-mode";
+ if (!isInTabletopMode) {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+ return;
+ }
+
+ // To prepare for the entry bounds.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ if (mTabletopModeController.getPreferredHalfInTabletopMode()
+ == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+ // Prefer top, avoid the bottom half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.centerY(),
+ displayBounds.right, displayBounds.bottom));
+ } else {
+ // Prefer bottom, avoid the top half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.top,
+ displayBounds.right, displayBounds.centerY()));
+ }
+
+ // Try to move the PiP window if we have entered PiP mode.
+ if (mPipTransitionState.hasEnteredPip()) {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+ // PiP bounds is too big to fit either half, bail early.
+ return;
+ }
+ mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+ }
+ });
+
mOneHandedController.ifPresent(controller -> {
controller.registerTransitionCallback(
new OneHandedTransitionCallback() {
@@ -714,12 +778,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
- if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ if (!mPipDisplayLayoutState.getDisplayLayout().isSameGeometry(layout)) {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
// cancel any running animator, as it is using stale display layout information
- PipAnimationController.quietCancel(animator);
+ animator.cancel();
}
onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
@@ -728,11 +792,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
- && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+ && mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation();
// update the internal state of objects subscribed to display changes
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null;
@@ -756,11 +819,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged = mPipBoundsState.getDisplayLayout().densityDpi() != 0
- && (mPipBoundsState.getDisplayLayout().densityDpi() != layout.densityDpi());
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
final float scale = (float) layout.densityDpi()
- / mPipBoundsState.getDisplayLayout().densityDpi();
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
postChangeBounds.set(0, 0,
(int) (postChangeBounds.width() * scale),
(int) (postChangeBounds.height() * scale));
@@ -775,8 +840,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
pipSnapAlgorithm.applySnapFraction(postChangeBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(),
mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
@@ -874,6 +939,23 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipBoundsState.getDisplayBounds().bottom - height,
+ mPipBoundsState.getDisplayBounds().right,
+ mPipBoundsState.getDisplayBounds().bottom);
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+ } else {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+ }
+ updatePipPositionForKeepClearAreas();
+ }
+
+ private void setLauncherAppIconSize(int iconSizePx) {
+ mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+ }
+
private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mOnIsInPipStateChangedListener = callback;
if (mOnIsInPipStateChangedListener != null) {
@@ -886,12 +968,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
}
- private void setPinnedStackAnimationType(int animationType) {
- mPipTaskOrganizer.setOneShotAnimationType(animationType);
- mPipTransitionController.setIsFullAnimation(
- animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
- }
-
@VisibleForTesting
void setPinnedStackAnimationListener(PipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
@@ -1018,7 +1094,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
// passing to mTouchHandler/mPipTaskOrganizer
final Rect outBounds = new Rect(toBounds);
- final int rotation = mPipBoundsState.getDisplayLayout().rotation();
+ final int rotation = mPipDisplayLayoutState.getDisplayLayout().rotation();
mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
@@ -1042,11 +1118,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
// Update the display layout, note that we have to do this on every rotation even if we
// aren't in PIP since we need to update the display layout to get the right resources
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
}
/**
@@ -1059,7 +1131,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Rect outInsetBounds,
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current display
- if ((displayId != mPipBoundsState.getDisplayId()) || (fromRotation == toRotation)) {
+ if ((displayId != mPipDisplayLayoutState.getDisplayId()) || (fromRotation == toRotation)) {
return false;
}
@@ -1083,11 +1155,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Update the display layout
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1095,8 +1163,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
postChangeStackBounds, false /* adjustForIme */);
pipSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
mPipBoundsAlgorithm.getInsetBounds(outInsetBounds);
outBounds.set(postChangeStackBounds);
@@ -1114,6 +1182,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
mPipSizeSpecHandler.dump(pw, innerPrefix);
+ mPipDisplayLayoutState.dump(pw, innerPrefix);
}
/**
@@ -1225,18 +1294,26 @@ public class PipController implements PipTransitionController.PipTransitionCallb
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
- (controller) -> {
- controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
- overlay);
- });
+ (controller) -> controller.stopSwipePipToHome(
+ taskId, componentName, destinationBounds, overlay));
}
@Override
public void setShelfHeight(boolean visible, int height) {
executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
- (controller) -> {
- controller.setShelfHeight(visible, height);
- });
+ (controller) -> controller.setShelfHeight(visible, height));
+ }
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
+ (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height));
+ }
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize",
+ (controller) -> controller.setLauncherAppIconSize(iconSizePx));
}
@Override
@@ -1254,9 +1331,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void setPipAnimationTypeToAlpha() {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
- (controller) -> {
- controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
- });
+ (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+ ANIM_TYPE_ALPHA));
}
}
}
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 979b7c7dc31f..167c0321d3ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -282,7 +282,8 @@ public class PipMenuView extends FrameLayout {
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
- && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+ && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
+ taskInfo.taskId);
mFocusedTaskAllowSplitScreen = isSplitScreen
|| (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& taskInfo.supportsMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index fee9140d0c2c..956af709f156 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -580,8 +580,16 @@ public class PipResizeGestureHandler {
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // disable the pinch resizing until the final bounds are updated
+ final boolean prevEnablePinchResize = mEnablePinchResize;
+ mEnablePinchResize = false;
+
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+ // reset the pinch resizing to its default state
+ mEnablePinchResize = prevEnablePinchResize;
+ });
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index d03d075b38af..82fe38ccc7b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.pip.PipUtils.dpToPx;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -31,6 +32,7 @@ import android.util.Size;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import java.io.PrintWriter;
@@ -40,10 +42,9 @@ import java.io.PrintWriter;
public class PipSizeSpecHandler {
private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
- @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
- @VisibleForTesting
- final SizeSpecSource mSizeSpecSourceImpl;
+ private final SizeSpecSource mSizeSpecSourceImpl;
/** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
@@ -212,24 +213,25 @@ public class PipSizeSpecHandler {
*/
@Override
public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- // getting the percentage of the max size that current size takes
float currAspectRatio = (float) size.getWidth() / size.getHeight();
+
+ // getting the percentage of the max size that current size takes
Size currentMaxSize = getMaxSize(currAspectRatio);
float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
// getting the max size for the target aspect ratio
Size updatedMaxSize = getMaxSize(aspectRatio);
- int width = (int) (updatedMaxSize.getWidth() * currentPercent);
- int height = (int) (updatedMaxSize.getHeight() * currentPercent);
+ int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
+ int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
// adjust the dimensions if below allowed min edge size
if (width < getMinEdgeSize() && aspectRatio <= 1) {
width = getMinEdgeSize();
- height = (int) (width / aspectRatio);
+ height = Math.round(width / aspectRatio);
} else if (height < getMinEdgeSize() && aspectRatio > 1) {
height = getMinEdgeSize();
- width = (int) (height * aspectRatio);
+ width = Math.round(height * aspectRatio);
}
// reduce the dimensions of the updated size to the calculated percentage
@@ -361,14 +363,12 @@ public class PipSizeSpecHandler {
}
}
- public PipSizeSpecHandler(Context context) {
+ public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
-
- boolean enablePipSizeLargeScreen = SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
+ mPipDisplayLayoutState = pipDisplayLayoutState;
// choose between two implementations of size spec logic
- if (enablePipSizeLargeScreen) {
+ if (supportsPipSizeLargeScreen()) {
mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
} else {
mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
@@ -403,15 +403,9 @@ public class PipSizeSpecHandler {
mSizeSpecSourceImpl.reloadResources();
}
- /** Returns the display's bounds. */
@NonNull
- public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ private Rect getDisplayBounds() {
+ return mPipDisplayLayoutState.getDisplayBounds();
}
public Point getScreenEdgeInsets() {
@@ -423,11 +417,12 @@ public class PipSizeSpecHandler {
*/
public Rect getInsetBounds() {
Rect insetBounds = new Rect();
- Rect insets = mDisplayLayout.stableInsets();
+ DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
+ Rect insets = displayLayout.stableInsets();
insetBounds.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
- mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
- mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ displayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
return insetBounds;
}
@@ -518,12 +513,24 @@ public class PipSizeSpecHandler {
}
}
+ @VisibleForTesting
+ boolean supportsPipSizeLargeScreen() {
+ // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
+ // can be injected
+ return SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
+ }
+
+ private boolean isTv() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
/** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
- pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+ pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+ pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 0e8d13d9979d..466da0e85358 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
@@ -71,8 +71,13 @@ public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
- private static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+ private boolean mEnablePipKeepClearAlgorithm =
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+
+ @VisibleForTesting
+ void setEnablePipKeepClearAlgorithm(boolean value) {
+ mEnablePipKeepClearAlgorithm = value;
+ }
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
@@ -427,7 +432,7 @@ public class PipTouchHandler {
if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
- } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+ } else if (mEnablePipKeepClearAlgorithm) {
// Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
// now are accounted for in the keep clear algorithm calculations
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
index 222307fba8c2..5f6b3fe1e250 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -31,6 +31,12 @@ import java.util.Objects;
abstract class TvPipAction {
+ /**
+ * Extras key for adding a boolean to the {@link Notification.Action} to differentiate custom
+ * from system actions, most importantly to identify custom close actions.
+ **/
+ public static final String EXTRA_IS_PIP_CUSTOM_ACTION = "EXTRA_IS_PIP_CUSTOM_ACTION";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ACTION_"}, value = {
ACTION_FULLSCREEN,
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 fa62a73ca9b4..3b44f10ebe62 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
@@ -186,16 +186,17 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
}
@VisibleForTesting(visibility = PACKAGE)
- public void onPipExpansionToggled(boolean expanded) {
+ public void updatePipExpansionState(boolean expanded) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
- mExpandCollapseAction.update(
+ boolean changed = mExpandCollapseAction.update(
expanded ? R.string.pip_collapse : R.string.pip_expand,
expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
-
- notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
- mActionsList.indexOf(mExpandCollapseAction));
+ if (changed) {
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+ mActionsList.indexOf(mExpandCollapseAction));
+ }
}
List<TvPipAction> getActionsList() {
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
new file mode 100644
index 000000000000..0221db836dda
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
@@ -0,0 +1,106 @@
+/*
+ * 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.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * This view is part of the Tv PiP menu. It is drawn behind the PiP surface and serves as a
+ * background behind the PiP content. If the PiP content is translucent, this view is visible
+ * behind it.
+ * It is also used to draw the shadow behind the Tv PiP menu. The shadow intensity is determined
+ * by the menu mode that the Tv PiP menu is in. See {@link TvPipMenuController.TvPipMenuMode}.
+ */
+class TvPipBackgroundView extends FrameLayout {
+ private static final String TAG = "TvPipBackgroundView";
+
+ private final View mBackgroundView;
+ private final int mElevationNoMenu;
+ private final int mElevationMoveMenu;
+ private final int mElevationAllActionsMenu;
+ private final int mPipMenuFadeAnimationDuration;
+
+ private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+
+ TvPipBackgroundView(@NonNull Context context) {
+ super(context, null, 0, 0);
+ inflate(context, R.layout.tv_pip_menu_background, this);
+
+ mBackgroundView = findViewById(R.id.background_view);
+
+ final Resources res = mContext.getResources();
+ mElevationNoMenu = res.getDimensionPixelSize(R.dimen.pip_menu_elevation_no_menu);
+ mElevationMoveMenu = res.getDimensionPixelSize(R.dimen.pip_menu_elevation_move_menu);
+ mElevationAllActionsMenu =
+ res.getDimensionPixelSize(R.dimen.pip_menu_elevation_all_actions_menu);
+ mPipMenuFadeAnimationDuration =
+ res.getInteger(R.integer.tv_window_menu_fade_animation_duration);
+ }
+
+ void transitionToMenuMode(@TvPipMenuController.TvPipMenuMode int pipMenuMode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: transitionToMenuMode(), old menu mode = %s, new menu mode = %s",
+ TAG, TvPipMenuController.getMenuModeString(mCurrentMenuMode),
+ TvPipMenuController.getMenuModeString(pipMenuMode));
+
+ if (mCurrentMenuMode == pipMenuMode) return;
+
+ int elevation = mElevationNoMenu;
+ Interpolator interpolator = TvPipInterpolators.ENTER;
+ switch(pipMenuMode) {
+ case MODE_NO_MENU:
+ elevation = mElevationNoMenu;
+ interpolator = TvPipInterpolators.EXIT;
+ break;
+ case MODE_MOVE_MENU:
+ elevation = mElevationMoveMenu;
+ break;
+ case MODE_ALL_ACTIONS_MENU:
+ elevation = mElevationAllActionsMenu;
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ interpolator = TvPipInterpolators.EXIT;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TV PiP menu mode: " + pipMenuMode);
+ }
+
+ mBackgroundView.animate()
+ .translationZ(elevation)
+ .setInterpolator(interpolator)
+ .setDuration(mPipMenuFadeAnimationDuration)
+ .start();
+
+ mCurrentMenuMode = pipMenuMode;
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 22b3f4987e82..e1737eccc6e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -31,6 +31,7 @@ import android.view.View;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
@@ -75,8 +76,9 @@ public class TvPipBoundsState extends PipBoundsState {
private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
- super(context, pipSizeSpecHandler);
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
+ super(context, pipSizeSpecHandler, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
mPreviousCollapsedGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index a437a3bc2826..02eeb2ac4fd5 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
@@ -51,11 +51,11 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -119,7 +119,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
@@ -154,7 +154,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -174,7 +174,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -196,7 +196,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -220,10 +220,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mTvPipBoundsState = tvPipBoundsState;
- mTvPipBoundsState.setDisplayLayout(layout);
- mTvPipBoundsState.setDisplayId(context.getDisplayId());
- mPipSizeSpecHandler = pipSizeSpecHandler;
- mPipSizeSpecHandler.setDisplayLayout(layout);
+
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayId(context.getDisplayId());
+
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
@@ -392,7 +393,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
- if (mTvPipBoundsState.getDisplayId() == displayId) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
boolean unrestrictedAreasChanged = !Objects.equals(unrestricted,
mTvPipBoundsState.getUnrestrictedKeepClearAreas());
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
@@ -445,7 +446,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: PiP has already been closed by custom close action", TAG);
return;
}
- removeTask(mPinnedTaskId);
+ mPipTaskOrganizer.removePip();
onPipDisappeared();
}
@@ -501,7 +502,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
mTvPipMenuController.onPipTransitionFinished(
PipAnimationController.isInPipDirection(direction));
- mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+ mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded());
}
@Override
@@ -514,7 +515,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onPipTransition_Finished(), state=%s, direction=%d",
TAG, stateToName(mState), direction);
mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
- mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+ mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded());
}
private void updateExpansionState() {
@@ -672,17 +673,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
- private static void removeTask(int taskId) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: removeTask(), taskId=%d", TAG, taskId);
- try {
- ActivityTaskManager.getService().removeTask(taskId);
- } catch (Exception e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Atm.removeTask() failed, %s", TAG, e);
- }
- }
-
private static String stateToName(@State int state) {
switch (state) {
case STATE_NO_PIP:
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 449a2bf09881..977aad4a898a 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
@@ -60,6 +60,7 @@ public class TvPipCustomAction extends TvPipAction {
}
button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ button.setIsCustomCloseAction(isCloseAction());
}
PendingIntent getPendingIntent() {
@@ -85,6 +86,7 @@ public class TvPipCustomAction extends TvPipAction {
Bundle extras = new Bundle();
extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
mRemoteAction.getContentDescription());
+ extras.putBoolean(TvPipAction.EXTRA_IS_PIP_CUSTOM_ACTION, true);
builder.addExtras(extras);
builder.setSemanticAction(isCloseAction()
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 73123b153382..b2a189b45d6c 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
@@ -18,7 +18,7 @@ package com.android.wm.shell.pip.tv;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
-import android.app.ActivityManager;
+import android.annotation.IntDef;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -27,15 +27,16 @@ import android.content.IntentFilter;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Handler;
-import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.SurfaceSyncGroup;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
@@ -60,14 +61,37 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
- private View mPipBackgroundView;
+ private TvPipBackgroundView mPipBackgroundView;
+ private boolean mMenuIsFocused;
- private boolean mMenuIsOpen;
- // User can actively move the PiP via the DPAD.
- private boolean mInMoveMode;
- // Used when only showing the move menu since we want to close the menu completely when
- // exiting the move menu instead of showing the regular button menu.
- private boolean mCloseAfterExitMoveMenu;
+ @TvPipMenuMode
+ private int mCurrentMenuMode = MODE_NO_MENU;
+ @TvPipMenuMode
+ private int mPrevMenuMode = MODE_NO_MENU;
+
+ @IntDef(prefix = { "MODE_" }, value = {
+ MODE_NO_MENU,
+ MODE_MOVE_MENU,
+ MODE_ALL_ACTIONS_MENU,
+ })
+ public @interface TvPipMenuMode {}
+
+ /**
+ * In this mode the PiP menu is not focused and no user controls are displayed.
+ */
+ static final int MODE_NO_MENU = 0;
+
+ /**
+ * In this mode the PiP menu is focused and the user can use the DPAD controls to move the PiP
+ * to a different position on the screen. We draw arrows in all possible movement directions.
+ */
+ static final int MODE_MOVE_MENU = 1;
+
+ /**
+ * In this mode the PiP menu is focused and we display an array of actions that the user can
+ * select. See {@link TvPipActionsProvider} for the types of available actions.
+ */
+ static final int MODE_ALL_ACTIONS_MENU = 2;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, Handler mainHandler) {
@@ -143,18 +167,27 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
"%s: Actions provider is not set", TAG);
return;
}
- mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ mPipMenuView = createTvPipMenuView();
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
}
+ @VisibleForTesting
+ TvPipMenuView createTvPipMenuView() {
+ return new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ }
+
private void attachPipBackgroundView() {
- mPipBackgroundView = LayoutInflater.from(mContext)
- .inflate(R.layout.tv_pip_menu_background, null);
+ mPipBackgroundView = createTvPipBackgroundView();
setUpViewSurfaceZOrder(mPipBackgroundView, -1);
addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
}
+ @VisibleForTesting
+ TvPipBackgroundView createTvPipBackgroundView() {
+ return new TvPipBackgroundView(mContext);
+ }
+
private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -170,8 +203,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void addPipMenuViewToSystemWindows(View v, String title) {
- mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */,
- 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+ final WindowManager.LayoutParams layoutParams =
+ getPipMenuLayoutParams(mContext, title, 0 /* width */, 0 /* height */);
+ layoutParams.alpha = 0f;
+ mSystemWindows.addView(v, layoutParams, 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
}
void onPipTransitionFinished(boolean enterTransition) {
@@ -188,37 +223,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementMenuOnly()", TAG);
- setInMoveMode(true);
- if (mMenuIsOpen) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
- }
+ "%s: showMovementMenu()", TAG);
+ switchToMenuMode(MODE_MOVE_MENU);
}
@Override
public void showMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- showMenuInternal();
- }
-
- private void showMenuInternal() {
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = true;
- grantPipMenuFocus(true);
- if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
- }
- mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
+ switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -228,9 +240,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
void updateGravity(int gravity) {
- if (mInMoveMode) {
- mPipMenuView.showMovementHints(gravity);
- }
+ mPipMenuView.setPipGravity(gravity);
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -240,58 +250,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void closeMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
-
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = false;
- mPipMenuView.hideAllUserControls();
- grantPipMenuFocus(false);
- mDelegate.onMenuClosed();
- }
-
- boolean isInMoveMode() {
- return mInMoveMode;
- }
-
- private void setInMoveMode(boolean moveMode) {
- if (mInMoveMode == moveMode) {
- return;
- }
- mInMoveMode = moveMode;
- if (mDelegate != null) {
- mDelegate.onInMoveModeChanged();
- }
- }
-
- @Override
- public boolean onExitMoveMode() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - %b, close when exiting move menu: %b",
- TAG, mInMoveMode, mCloseAfterExitMoveMenu);
-
- if (mInMoveMode) {
- setInMoveMode(false);
- if (mCloseAfterExitMoveMenu) {
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPipMovement(int keycode) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipMovement - %b", TAG, mInMoveMode);
- if (mInMoveMode) {
- mDelegate.movePip(keycode);
- }
- return mInMoveMode;
+ switchToMenuMode(MODE_NO_MENU);
}
@Override
@@ -320,15 +279,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
Rect pipBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: resizePipMenu: %s", TAG, pipBounds.toShortString());
+
if (pipBounds.isEmpty()) {
return;
}
-
- if (!isMenuReadyToMove()) {
+ if (!isMenuAttached()) {
return;
}
-
final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
@@ -354,9 +312,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
- Rect pipBounds) {
+ Rect pipBounds, float alpha) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipMenu: %s", TAG, pipBounds.toShortString());
+ "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
if (pipBounds.isEmpty()) {
if (pipTx == null) {
@@ -365,7 +323,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
return;
}
- if (!isMenuReadyToMove()) {
+ if (!isMenuAttached()) {
return;
}
@@ -378,6 +336,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+ if (alpha != ALPHA_NO_CHANGE) {
+ pipTx.setAlpha(frontSurface, alpha);
+ pipTx.setAlpha(backSurface, alpha);
+ }
+
// Synchronize drawing the content in the front and back surfaces together with the pip
// transaction and the position change for the front and back surfaces
final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
@@ -388,12 +351,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
syncGroup.markSyncReady();
}
- private boolean isMenuReadyToMove() {
+ private boolean isMenuAttached() {
final boolean ready = mPipMenuView != null && mPipMenuView.getViewRootImpl() != null
&& mPipBackgroundView != null && mPipBackgroundView.getViewRootImpl() != null;
if (!ready) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
+ "%s: the menu surfaces are not attached.", TAG);
}
return ready;
}
@@ -412,6 +375,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void updateMenuBounds(Rect pipBounds) {
+ if (!isMenuAttached()) {
+ return;
+ }
final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
@@ -422,13 +388,91 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
if (mPipMenuView != null) {
- mPipMenuView.updateBounds(pipBounds);
+ mPipMenuView.setPipBounds(pipBounds);
}
}
+ // Start methods handling {@link TvPipMenuMode}
+
+ @VisibleForTesting
+ boolean isMenuOpen() {
+ return mCurrentMenuMode != MODE_NO_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInMoveMode() {
+ return mCurrentMenuMode == MODE_MOVE_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInAllActionsMode() {
+ return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+ switchToMenuMode(menuMode, false);
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
+ // Note: we intentionally don't return early here, because the TvPipMenuView needs to
+ // refresh the Ui even if there is no menu mode change.
+ mPrevMenuMode = mCurrentMenuMode;
+ mCurrentMenuMode = menuMode;
+
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
+ getMenuModeString(), getMenuModeString(mPrevMenuMode));
+
+ updateUiOnNewMenuModeRequest(resetMenu);
+ updateDelegateOnNewMenuModeRequest();
+ }
+
+ private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
+ if (mPipMenuView == null || mPipBackgroundView == null) return;
+
+ mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+ mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+ mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
+ grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
+ }
+
+ private void updateDelegateOnNewMenuModeRequest() {
+ if (mPrevMenuMode == mCurrentMenuMode) return;
+ if (mDelegate == null) return;
+
+ if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+ mDelegate.onInMoveModeChanged();
+ }
+
+ if (mCurrentMenuMode == MODE_NO_MENU) {
+ mDelegate.onMenuClosed();
+ }
+ }
+
+ @VisibleForTesting
+ String getMenuModeString() {
+ return getMenuModeString(mCurrentMenuMode);
+ }
+
+ static String getMenuModeString(@TvPipMenuMode int menuMode) {
+ switch(menuMode) {
+ case MODE_NO_MENU:
+ return "MODE_NO_MENU";
+ case MODE_MOVE_MENU:
+ return "MODE_MOVE_MENU";
+ case MODE_ALL_ACTIONS_MENU:
+ return "MODE_ALL_ACTIONS_MENU";
+ default:
+ return "Unknown";
+ }
+ }
+
+ // Start {@link TvPipMenuView.Delegate} methods
+
@Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
+ public void onCloseEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mDelegate.closeEduText();
}
@Override
@@ -439,9 +483,35 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onCloseEduText() {
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
- mDelegate.closeEduText();
+ public boolean onExitMoveMode() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+
+ final int saveMenuMode = mCurrentMenuMode;
+ if (isInMoveMode()) {
+ switchToMenuMode(mPrevMenuMode);
+ }
+ return saveMenuMode == MODE_MOVE_MENU;
+ }
+
+ @Override
+ public boolean onPipMovement(int keycode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
+ if (isInMoveMode()) {
+ mDelegate.movePip(keycode);
+ }
+ return isInMoveMode();
+ }
+
+ @Override
+ public void onPipWindowFocusChanged(boolean focused) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+ mMenuIsFocused = focused;
+ if (!focused && isMenuOpen()) {
+ closeMenu();
+ }
}
interface Delegate {
@@ -455,6 +525,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void grantPipMenuFocus(boolean grantFocus) {
+ if (mMenuIsFocused == grantFocus) return;
+
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: grantWindowFocus(%b)", TAG, grantFocus);
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 6eef22562caa..f86f987039ba 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
@@ -23,6 +23,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -115,6 +116,10 @@ class TvPipMenuEduTextDrawer extends FrameLayout {
scheduleLifecycleEvents();
}
+ int getEduTextDrawerHeight() {
+ return getVisibility() == GONE ? 0 : getHeight();
+ }
+
private void scheduleLifecycleEvents() {
final int startScrollDelay = mContext.getResources().getInteger(
R.integer.pip_edu_text_start_scroll_delay);
@@ -226,20 +231,41 @@ class TvPipMenuEduTextDrawer extends FrameLayout {
.start();
// Start animation to close the drawer by animating its height to 0
- final ValueAnimator heightAnimation = ValueAnimator.ofInt(getHeight(), 0);
- heightAnimation.setDuration(eduTextSlideExitAnimationDuration);
- heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
- heightAnimation.addUpdateListener(animator -> {
+ final ValueAnimator heightAnimator = ValueAnimator.ofInt(getHeight(), 0);
+ heightAnimator.setDuration(eduTextSlideExitAnimationDuration);
+ heightAnimator.setInterpolator(TvPipInterpolators.BROWSE);
+ heightAnimator.addUpdateListener(animator -> {
final ViewGroup.LayoutParams params = getLayoutParams();
params.height = (int) animator.getAnimatedValue();
setLayoutParams(params);
- if (params.height == 0) {
- setVisibility(GONE);
+ });
+ heightAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(@NonNull Animator animator) {
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animator) {
+ onCloseEduTextAnimationEnd();
+ }
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animator) {
+ onCloseEduTextAnimationEnd();
+ }
+
+ @Override
+ public void onAnimationRepeat(@NonNull Animator animator) {
}
});
- heightAnimation.start();
+ heightAnimator.start();
+
+ mListener.onCloseEduTextAnimationStart();
+ }
- mListener.onCloseEduText();
+ public void onCloseEduTextAnimationEnd() {
+ setVisibility(GONE);
+ mListener.onCloseEduTextAnimationEnd();
}
/**
@@ -270,11 +296,8 @@ class TvPipMenuEduTextDrawer extends FrameLayout {
* A listener for edu text drawer event states.
*/
interface Listener {
- /**
- * The edu text closing impacts the size of the Picture-in-Picture window and influences
- * how it is positioned on the screen.
- */
- void onCloseEduText();
+ void onCloseEduTextAnimationStart();
+ void onCloseEduTextAnimationEnd();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 56c602a1d4f3..d07641892552 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
@@ -26,6 +26,9 @@ import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
import android.content.Context;
import android.graphics.Rect;
@@ -54,7 +57,8 @@ import java.util.List;
* A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
* the TvPipActionsProvider as well as the buttons for manually moving the PiP.
*/
-public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener,
+ TvPipMenuEduTextDrawer.Listener {
private static final String TAG = "TvPipMenuView";
private final TvPipMenuView.Listener mListener;
@@ -73,6 +77,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private final View mDimLayer;
private final TvPipMenuEduTextDrawer mEduTextDrawer;
+ private final ViewGroup mEduTextContainer;
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
@@ -86,10 +91,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private Rect mCurrentPipBounds;
- private boolean mMoveMenuIsVisible;
- private boolean mButtonMenuIsVisible;
- private boolean mSwitchingOrientation;
+ private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+ private final Rect mCurrentPipBounds = new Rect();
+ private int mCurrentPipGravity;
private final AccessibilityManager mA11yManager;
private final Handler mMainHandler;
@@ -137,9 +141,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mPipMenuBorderWidth = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
- mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, mListener);
- ((FrameLayout) findViewById(R.id.tv_pip_menu_edu_text_drawer_placeholder))
- .addView(mEduTextDrawer);
+ mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, this);
+ mEduTextContainer = (ViewGroup) findViewById(R.id.tv_pip_menu_edu_text_container);
+ mEduTextContainer.addView(mEduTextDrawer);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -172,18 +176,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
return;
}
- if (mButtonMenuIsVisible) {
- mSwitchingOrientation = true;
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) {
+ // Fade out while orientation change is ongoing and fade back in once transition is
+ // finished.
mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
- .setDuration(mResizeAnimationDuration / 2)
- .withEndAction(() -> {
- mButtonLayoutManager.setOrientation(vertical
- ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
- // Only make buttons visible again in onPipTransitionFinished to keep in
- // sync with PiP content alpha animation.
- });
+ .setDuration(mResizeAnimationDuration / 2);
} else {
mButtonLayoutManager.setOrientation(vertical
? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
@@ -205,31 +204,29 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.init();
}
- if (mSwitchingOrientation) {
+ mButtonLayoutManager.setOrientation(
+ mCurrentPipBounds.height() > mCurrentPipBounds.width()
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU
+ && mActionButtonsRecyclerView.getAlpha() != 1f) {
mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
}
- mSwitchingOrientation = false;
}
/**
* Also updates the button gravity.
*/
- void updateBounds(Rect updatedBounds) {
+ void setPipBounds(Rect updatedPipBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
- updatedBounds.height());
- mCurrentPipBounds = updatedBounds;
- updatePipFrameBounds();
- }
+ "%s: updateLayout, width: %s, height: %s", TAG, updatedPipBounds.width(),
+ updatedPipBounds.height());
+ if (updatedPipBounds.equals(mCurrentPipBounds)) return;
- Rect getPipMenuContainerBounds(Rect pipBounds) {
- final Rect menuUiBounds = new Rect(pipBounds);
- menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
- menuUiBounds.bottom += mEduTextDrawer.getHeight();
- return menuUiBounds;
+ mCurrentPipBounds.set(updatedPipBounds);
+ updatePipFrameBounds();
}
/**
@@ -240,11 +237,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
* pip menu when it gains focus.
*/
private void updatePipFrameBounds() {
- final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
- if (pipFrameParams != null) {
- pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
- pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
- mPipFrameView.setLayoutParams(pipFrameParams);
+ if (mPipFrameView.getVisibility() == VISIBLE) {
+ final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
+ if (pipFrameParams != null) {
+ pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
+ pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
+ mPipFrameView.setLayoutParams(pipFrameParams);
+ }
}
final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
@@ -264,12 +263,39 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
}
}
- /**
- * @param gravity for the arrow hints
- */
- void showMoveMenu(int gravity) {
+ Rect getPipMenuContainerBounds(Rect pipBounds) {
+ final Rect menuUiBounds = new Rect(pipBounds);
+ menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+ menuUiBounds.bottom += mEduTextDrawer.getEduTextDrawerHeight();
+ return menuUiBounds;
+ }
+
+ void transitionToMenuMode(int menuMode, boolean resetMenu) {
+ switch (menuMode) {
+ case MODE_NO_MENU:
+ hideAllUserControls();
+ break;
+ case MODE_MOVE_MENU:
+ showMoveMenu();
+ break;
+ case MODE_ALL_ACTIONS_MENU:
+ showAllActionsMenu(resetMenu);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TV PiP menu mode: "
+ + TvPipMenuController.getMenuModeString(mCurrentMenuMode));
+ }
+
+ mCurrentMenuMode = menuMode;
+ }
+
+ private void showMoveMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showMovementHints(gravity);
+
+ if (mCurrentMenuMode == MODE_MOVE_MENU) return;
+
+ showMovementHints();
setMenuButtonsVisible(false);
setFrameHighlighted(true);
@@ -278,32 +304,38 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.closeIfNeeded();
}
-
- void showButtonsMenu(boolean exitingMoveMode) {
+ private void showAllActionsMenu(boolean resetMenu) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+
+ if (resetMenu) {
+ scrollToFirstAction();
+ }
+
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
+
setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
animateAlphaTo(1f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
- if (exitingMoveMode) {
- scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
- /* alwaysScroll= */ false);
- } else {
- scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ refocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE));
}
- }
- private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrollAndRefocusButton, target: %d", TAG, position);
+ }
- if (alwaysScroll || !refocusButton(position)) {
- mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
- mActionButtonsRecyclerView.post(() -> refocusButton(position));
+ private void scrollToFirstAction() {
+ // Clearing the focus here is necessary to allow a smooth scroll even if the first action
+ // is currently not visible.
+ final View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ focusedChild.clearFocus();
}
+
+ mButtonLayoutManager.scrollToPosition(0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(0));
}
/**
@@ -311,6 +343,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
* the view for the position not being available (scrolling beforehand will be necessary).
*/
private boolean refocusButton(int position) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: refocusButton, position: %d", TAG, position);
+
View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
if (itemToFocus != null) {
itemToFocus.requestFocus();
@@ -319,21 +354,29 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
return itemToFocus != null;
}
- void hideAllUserControls() {
+ private void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
+
+ if (mCurrentMenuMode == MODE_NO_MENU) return;
+
setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
animateAlphaTo(0f, mDimLayer);
}
+ void setPipGravity(int gravity) {
+ mCurrentPipGravity = gravity;
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ showMovementHints();
+ }
+ }
+
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- hideAllUserControls();
- }
+ mListener.onPipWindowFocusChanged(hasWindowFocus);
}
private void animateAlphaTo(float alpha, View view) {
@@ -367,6 +410,17 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
}
@Override
+ public void onCloseEduTextAnimationStart() {
+ mListener.onCloseEduText();
+ }
+
+ @Override
+ public void onCloseEduTextAnimationEnd() {
+ mPipFrameView.setVisibility(GONE);
+ mEduTextContainer.setVisibility(GONE);
+ }
+
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP) {
@@ -399,15 +453,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
/**
* Shows user hints for moving the PiP, e.g. arrows.
*/
- public void showMovementHints(int gravity) {
+ public void showMovementHints() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
- mMoveMenuIsVisible = true;
-
- animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
- animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
- animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
- animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(mCurrentPipGravity));
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
boolean a11yEnabled = mA11yManager.isEnabled();
setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP);
@@ -446,10 +498,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideMovementHints()", TAG);
- if (!mMoveMenuIsVisible) {
- return;
- }
- mMoveMenuIsVisible = false;
+ if (mCurrentMenuMode != MODE_MOVE_MENU) return;
animateAlphaTo(0, mArrowUp);
animateAlphaTo(0, mArrowRight);
@@ -464,7 +513,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showUserActions: %b", TAG, visible);
- mButtonMenuIsVisible = visible;
animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
@@ -518,7 +566,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
}
}
- interface Listener extends TvPipMenuEduTextDrawer.Listener {
+ interface Listener {
void onBackPress();
@@ -534,5 +582,17 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
* @return whether pip movement was handled.
*/
boolean onPipMovement(int keycode);
+
+ /**
+ * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
+ * has lost focus.
+ */
+ void onPipWindowFocusChanged(boolean focused);
+
+ /**
+ * The edu text closing impacts the size of the Picture-in-Picture window and influences
+ * how it is positioned on the screen.
+ */
+ void onCloseEduText();
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
index 93b6a908e3f4..f6dabb762c54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -52,15 +52,21 @@ public class TvPipSystemAction extends TvPipAction {
broadcastAction);
}
- void update(@StringRes int title, @DrawableRes int icon) {
+ /**
+ * @return true if the title and/or icon were changed.
+ */
+ boolean update(@StringRes int title, @DrawableRes int icon) {
+ boolean changed = title != mTitleResource || icon != mIconResource;
mTitleResource = title;
mIconResource = icon;
+ return changed;
}
void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
button.setTextAndDescription(mTitleResource);
button.setImageResource(mIconResource);
button.setEnabled(true);
+ button.setIsCustomCloseAction(false);
}
PendingIntent getPendingIntent() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index be9b9361b359..4819f665d6d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -36,7 +37,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
@@ -51,7 +51,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -63,8 +63,8 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor mainExecutor) {
- super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
- boundsHandler, pipMenuController, pipAnimationController,
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
+ pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
mainExecutor);
@@ -85,4 +85,24 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
mPipParamsChangedForwarder.notifySubtitleChanged(params.getSubtitle());
}
}
+
+ /**
+ * Override for TV since the menu bounds affect the PiP location. Additionally, we want to
+ * ensure that menu is shown immediately since it should always be visible on TV as it creates
+ * a border with rounded corners around the PiP.
+ */
+ protected boolean shouldAttachMenuEarly() {
+ return true;
+ }
+
+ protected boolean shouldAlwaysFadeIn() {
+ return true;
+ }
+
+ @Override
+ protected boolean shouldSyncPipTransactionWithMenu() {
+ // We always have a menu visible and want to sync the pip transaction with the menu, even
+ // when the menu alpha is 0 (e.g. when a fade-in animation starts).
+ return true;
+ }
}
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 8ebcf63f36e9..d3253a5e4d94 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
@@ -16,60 +16,43 @@
package com.android.wm.shell.pip.tv;
-import android.app.TaskInfo;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
+import android.content.Context;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
+
/**
* PiP Transition for TV.
- * TODO: Implement animation once TV is using Transitions.
*/
-public class TvPipTransition extends PipTransitionController {
- public TvPipTransition(
+public class TvPipTransition extends PipTransition {
+
+ public TvPipTransition(Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
- PipBoundsState pipBoundsState,
- PipMenuController pipMenuController,
+ TvPipBoundsState tvPipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipTransitionState pipTransitionState,
+ TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
- super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController);
- }
-
- @Override
- public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, int direction,
- SurfaceControl.Transaction tx) {
-
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- return false;
+ PipAnimationController pipAnimationController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreenController> splitScreenOptional) {
+ super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
+ pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
+ tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ splitScreenOptional);
}
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c33af9..c4a0e9cf5a74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -32,6 +32,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
+ WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -50,6 +52,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 1a6c1d65db03..4048c5b8feab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -17,6 +17,11 @@
package com.android.wm.shell.recents;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -45,4 +50,10 @@ interface IRecentTasks {
* Gets the set of running tasks.
*/
RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+
+ /**
+ * Starts a recents transition.
+ */
+ oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) = 5;
}
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 0d9faa3c6f83..c5bfd8753994 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
@@ -24,13 +24,18 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RE
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import android.view.IRecentsAnimationRunner;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -79,6 +84,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
+ private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -150,6 +156,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
}
+ void setTransitionHandler(RecentsTransitionHandler handler) {
+ mTransitionHandler = handler;
+ }
+
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
@@ -492,5 +502,18 @@ public class RecentTasksController implements TaskStackListenerCallback,
true /* blocking */);
return tasks[0];
}
+
+ @Override
+ public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ if (mController.mTransitionHandler == null) {
+ Slog.e(TAG, "Used shell-transitions startRecentsTransition without"
+ + " shell-transitions");
+ return;
+ }
+ executeRemoteCallWithTaskPermission(mController, "startRecentsTransition",
+ (controller) -> controller.mTransitionHandler.startRecentsTransition(
+ intent, fillIn, options, appThread, listener));
+ }
}
-} \ No newline at end of file
+}
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
new file mode 100644
index 000000000000..b55487258220
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -0,0 +1,857 @@
+/*
+ * 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.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
+ * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
+ */
+public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "RecentsTransitionHandler";
+
+ private final Transitions mTransitions;
+ private final ShellExecutor mExecutor;
+ private IApplicationThread mAnimApp = null;
+ private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+
+ /**
+ * List of other handlers which might need to mix recents with other things. These are checked
+ * in the order they are added. Ideally there should only be one.
+ */
+ private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+
+ public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ @Nullable RecentTasksController recentTasksController) {
+ mTransitions = transitions;
+ mExecutor = transitions.getMainExecutor();
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (recentTasksController == null) return;
+ shellInit.addInitCallback(() -> {
+ recentTasksController.setTransitionHandler(this);
+ transitions.addHandler(this);
+ }, this);
+ }
+
+ /** Register a mixer handler. {@see RecentsMixedHandler}*/
+ public void addMixer(RecentsMixedHandler mixer) {
+ mMixers.add(mixer);
+ }
+
+ /** Unregister a Mixed Handler */
+ public void removeMixer(RecentsMixedHandler mixer) {
+ mMixers.remove(mixer);
+ }
+
+ void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startRecentsTransition");
+ // only care about latest one.
+ mAnimApp = appThread;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(intent, fillIn, options);
+ final RecentsController controller = new RecentsController(listener);
+ RecentsMixedHandler mixer = null;
+ Transitions.TransitionHandler mixedHandler = null;
+ for (int i = 0; i < mMixers.size(); ++i) {
+ mixedHandler = mMixers.get(i).handleRecentsRequest(wct);
+ if (mixedHandler != null) {
+ mixer = mMixers.get(i);
+ break;
+ }
+ }
+ final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
+ mixedHandler == null ? this : mixedHandler);
+ if (mixer != null) {
+ mixer.setRecentsTransition(transition);
+ }
+ if (transition == null) {
+ controller.cancel("startRecentsTransition");
+ return;
+ }
+ controller.setTransition(transition);
+ mControllers.add(controller);
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(IBinder transition,
+ TransitionRequestInfo request) {
+ // do not directly handle requests. Only entry point should be via startRecentsTransition
+ Slog.e(TAG, "RecentsTransitionHandler.handleRequest: Unexpected transition request");
+ return null;
+ }
+
+ private int findController(IBinder transition) {
+ for (int i = mControllers.size() - 1; i >= 0; --i) {
+ if (mControllers.get(i).mTransition == transition) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int controllerIdx = findController(transition);
+ if (controllerIdx < 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startAnimation: no controller found");
+ return false;
+ }
+ final RecentsController controller = mControllers.get(controllerIdx);
+ Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+ mAnimApp = null;
+ if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startAnimation: failed to start animation");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int targetIdx = findController(mergeTarget);
+ if (targetIdx < 0) return;
+ final RecentsController controller = mControllers.get(targetIdx);
+ controller.merge(info, t, finishCallback);
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted,
+ SurfaceControl.Transaction finishTransaction) {
+ final int idx = findController(transition);
+ if (idx < 0) return;
+ mControllers.get(idx).cancel("onTransitionConsumed");
+ }
+
+ /** There is only one of these and it gets reset on finish. */
+ private class RecentsController extends IRecentsAnimationController.Stub {
+ private IRecentsAnimationRunner mListener;
+ private IBinder.DeathRecipient mDeathHandler;
+ private Transitions.TransitionFinishCallback mFinishCB = null;
+ private SurfaceControl.Transaction mFinishTransaction = null;
+
+ /**
+ * List of tasks that we are switching away from via this transition. Upon finish, these
+ * pausing tasks will become invisible.
+ * These need to be ordered since the order must be restored if there is no task-switch.
+ */
+ private ArrayList<TaskState> mPausingTasks = null;
+
+ /**
+ * List of tasks that we are switching to. Upon finish, these will remain visible and
+ * on top.
+ */
+ private ArrayList<TaskState> mOpeningTasks = null;
+
+ private WindowContainerToken mPipTask = null;
+ private WindowContainerToken mRecentsTask = null;
+ private int mRecentsTaskId = -1;
+ private TransitionInfo mInfo = null;
+ private boolean mOpeningSeparateHome = false;
+ private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+ private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ private IBinder mTransition = null;
+ private boolean mKeyguardLocked = false;
+ private boolean mWillFinishToHome = false;
+
+ /** The animation is idle, waiting for the user to choose a task to switch to. */
+ private static final int STATE_NORMAL = 0;
+
+ /** The user chose a new task to switch to and the animation is animating to it. */
+ private static final int STATE_NEW_TASK = 1;
+
+ /** The latest state that the recents animation is operating in. */
+ private int mState = STATE_NORMAL;
+
+ RecentsController(IRecentsAnimationRunner listener) {
+ mListener = listener;
+ mDeathHandler = () -> {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.DeathRecipient: binder died");
+ finish(mWillFinishToHome, false /* leaveHint */);
+ };
+ try {
+ mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RecentsController: failed to link to death", e);
+ mListener = null;
+ }
+ }
+
+ void setTransition(IBinder transition) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.setTransition: id=%s", transition);
+ mTransition = transition;
+ }
+
+ void cancel(String reason) {
+ // restoring (to-home = false) involves submitting more WM changes, so by default, use
+ // toHome = true when canceling.
+ cancel(true /* toHome */, reason);
+ }
+
+ void cancel(boolean toHome, String reason) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.cancel: toHome=%b reason=%s", toHome, reason);
+ if (mListener != null) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.cancel: calling onAnimationCanceled");
+ mListener.onAnimationCanceled(null, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ }
+ }
+ if (mFinishCB != null) {
+ finishInner(toHome, false /* userLeave */);
+ } else {
+ cleanUp();
+ }
+ }
+
+ /**
+ * Sends a cancel message to the recents animation with snapshots. Used to trigger a
+ * "replace-with-screenshot" like behavior.
+ */
+ private boolean sendCancelWithSnapshots() {
+ int[] taskIds = null;
+ TaskSnapshot[] snapshots = null;
+ if (mPausingTasks.size() > 0) {
+ taskIds = new int[mPausingTasks.size()];
+ snapshots = new TaskSnapshot[mPausingTasks.size()];
+ try {
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
+ mPausingTasks.get(0).mTaskInfo.taskId);
+ }
+ } catch (RemoteException e) {
+ taskIds = null;
+ snapshots = null;
+ }
+ }
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.cancel: calling onAnimationCanceled with snapshots");
+ mListener.onAnimationCanceled(taskIds, snapshots);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ return false;
+ }
+ return true;
+ }
+
+ void cleanUp() {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.cleanup");
+ if (mListener != null && mDeathHandler != null) {
+ mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
+ mDeathHandler = null;
+ }
+ mListener = null;
+ mFinishCB = null;
+ // clean-up leash surfacecontrols and anything that might reference them.
+ if (mLeashMap != null) {
+ for (int i = 0; i < mLeashMap.size(); ++i) {
+ mLeashMap.valueAt(i).release();
+ }
+ mLeashMap = null;
+ }
+ mFinishTransaction = null;
+ mPausingTasks = null;
+ mOpeningTasks = null;
+ mInfo = null;
+ mTransition = null;
+ mControllers.remove(this);
+ }
+
+ boolean start(TransitionInfo info, SurfaceControl.Transaction t,
+ SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.start");
+ if (mListener == null || mTransition == null) {
+ cleanUp();
+ return false;
+ }
+ // First see if this is a valid recents transition.
+ boolean hasPausingTasks = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isWallpaper(change)) continue;
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ hasPausingTasks = true;
+ continue;
+ }
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ }
+ }
+ if (mRecentsTask == null && !hasPausingTasks) {
+ // Recents is already running apparently, so this is a no-op.
+ Slog.e(TAG, "Tried to start recents while it is already running.");
+ cleanUp();
+ return false;
+ }
+
+ mInfo = info;
+ mFinishCB = finishCB;
+ mFinishTransaction = finishT;
+ mPausingTasks = new ArrayList<>();
+ mOpeningTasks = new ArrayList<>();
+ mLeashMap = new ArrayMap<>();
+ mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
+ TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
+ // About layering: we divide up the "layer space" into 3 regions (each the size of
+ // the change count). This lets us categorize things into above/below/between
+ // while maintaining their relative ordering.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (TransitionUtil.isWallpaper(change)) {
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ // wallpapers go into the "below" layer space
+ info.getChanges().size() - i, info, t, mLeashMap);
+ wallpapers.add(target);
+ // Make all the wallpapers opaque since we want them visible from the start
+ t.setAlpha(target.leash, 1);
+ } else if (leafTaskFilter.test(change)) {
+ // start by putting everything into the "below" layer space.
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ info.getChanges().size() - i, info, t, mLeashMap);
+ apps.add(target);
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding pausing taskId=%d", taskInfo.taskId);
+ // raise closing (pausing) task to "above" layer so it isn't covered
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ mPausingTasks.add(new TaskState(change, target.leash));
+ if (taskInfo.pictureInPictureParams != null
+ && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+ mPipTask = taskInfo.token;
+ }
+ } else if (taskInfo != null
+ && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ // There's a 3p launcher, so make sure recents goes above that.
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // do nothing
+ } else if (TransitionUtil.isOpeningType(change.getMode())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding opening taskId=%d", taskInfo.taskId);
+ mOpeningTasks.add(new TaskState(change, target.leash));
+ }
+ }
+ }
+ t.apply();
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.start: calling onAnimationStart");
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
+ new Rect(0, 0, 0, 0), new Rect());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel("onAnimationStart() failed");
+ }
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ void merge(TransitionInfo info, SurfaceControl.Transaction t,
+ Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishCB == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.merge: skip, no finish callback");
+ // This was no-op'd (likely a repeated start) and we've already sent finish.
+ return;
+ }
+ if (info.getType() == TRANSIT_SLEEP) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.merge: transit_sleep");
+ // A sleep event means we need to stop animations immediately, so cancel here.
+ cancel("transit_sleep");
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.merge");
+ ArrayList<TransitionInfo.Change> openingTasks = null;
+ ArrayList<TransitionInfo.Change> closingTasks = null;
+ mOpeningSeparateHome = false;
+ TransitionInfo.Change recentsOpening = null;
+ boolean foundRecentsClosing = false;
+ boolean hasChangingApp = false;
+ final TransitionUtil.LeafTaskFilter leafTaskFilter =
+ new TransitionUtil.LeafTaskFilter();
+ boolean hasTaskChange = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Tasks that are always on top (e.g. bubbles), will handle their own transition
+ // as they are on top of everything else. So cancel the merge here.
+ cancel("task #" + taskInfo.taskId + " is always_on_top");
+ return;
+ }
+ hasTaskChange = hasTaskChange || taskInfo != null;
+ final boolean isLeafTask = leafTaskFilter.test(change);
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ recentsOpening = change;
+ } else if (isLeafTask) {
+ if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // This is usually a 3p launcher
+ mOpeningSeparateHome = true;
+ }
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ }
+ openingTasks.add(change);
+ }
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ foundRecentsClosing = true;
+ } else if (isLeafTask) {
+ if (closingTasks == null) {
+ closingTasks = new ArrayList<>();
+ }
+ closingTasks.add(change);
+ }
+ } else if (change.getMode() == TRANSIT_CHANGE) {
+ // Finish recents animation if the display is changed, so the default
+ // transition handler can play the animation such as rotation effect.
+ if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+ cancel(mWillFinishToHome, "display change");
+ return;
+ }
+ // Don't consider order-only changes as changing apps.
+ if (!TransitionUtil.isOrderOnly(change)) {
+ hasChangingApp = true;
+ }
+ }
+ }
+ if (hasChangingApp && foundRecentsClosing) {
+ // This happens when a visible app is expanding (usually PiP). In this case,
+ // that transition probably has a special-purpose animation, so finish recents
+ // now and let it do its animation (since recents is going to be occluded).
+ sendCancelWithSnapshots();
+ mExecutor.executeDelayed(
+ () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+ return;
+ }
+ if (recentsOpening != null) {
+ // the recents task re-appeared. This happens if the user gestures before the
+ // task-switch (NEW_TASK) animation finishes.
+ if (mState == STATE_NORMAL) {
+ Slog.e(TAG, "Returning to recents while recents is already idle.");
+ }
+ if (closingTasks == null || closingTasks.size() == 0) {
+ Slog.e(TAG, "Returning to recents without closing any opening tasks.");
+ }
+ // Setup may hide it initially since it doesn't know that overview was still active.
+ t.show(recentsOpening.getLeash());
+ t.setAlpha(recentsOpening.getLeash(), 1.f);
+ mState = STATE_NORMAL;
+ }
+ boolean didMergeThings = false;
+ if (closingTasks != null) {
+ // Potentially cancelling a task-switch. Move the tasks back to mPausing if they
+ // are in mOpening.
+ for (int i = 0; i < closingTasks.size(); ++i) {
+ final TransitionInfo.Change change = closingTasks.get(i);
+ int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+ if (openingIdx < 0) {
+ Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
+ + " something unexpected: " + change.getTaskInfo().taskId);
+ continue;
+ }
+ final TaskState openingTask = mOpeningTasks.remove(openingIdx);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " pausing opening taskId=%d", openingTask.mTaskInfo.taskId);
+ mPausingTasks.add(openingTask);
+ didMergeThings = true;
+ }
+ }
+ RemoteAnimationTarget[] appearedTargets = null;
+ if (openingTasks != null && openingTasks.size() > 0) {
+ // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+ // enter NEW_TASK state since this will start the switch-to animation.
+ final int layer = mInfo.getChanges().size() * 3;
+ appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+ for (int i = 0; i < openingTasks.size(); ++i) {
+ final TransitionInfo.Change change = openingTasks.get(i);
+ int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ if (pausingIdx >= 0) {
+ // Something is showing/opening a previously-pausing app.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ final TaskState pausingTask = mPausingTasks.remove(pausingIdx);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " opening pausing taskId=%d", pausingTask.mTaskInfo.taskId);
+ mOpeningTasks.add(pausingTask);
+ // Setup hides opening tasks initially, so make it visible again (since we
+ // are already showing it).
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else {
+ // We are receiving new opening tasks, so convert to onTasksAppeared.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, info, t, mLeashMap);
+ // reparent into the original `mInfo` since that's where we are animating.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
+ t.setLayer(appearedTargets[i].leash, layer);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " opening new taskId=%d", appearedTargets[i].taskId);
+ mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+ }
+ }
+ didMergeThings = true;
+ mState = STATE_NEW_TASK;
+ }
+ if (!hasTaskChange) {
+ // Activity only transition, so consume the merge as it doesn't affect the rest of
+ // recents.
+ Slog.d(TAG, "Got an activity only transition during recents, so apply directly");
+ mergeActivityOnly(info, t);
+ } else if (!didMergeThings) {
+ // Didn't recognize anything in incoming transition so don't merge it.
+ Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing="
+ + foundRecentsClosing);
+ if (foundRecentsClosing) {
+ mWillFinishToHome = false;
+ cancel(false /* toHome */, "didn't merge");
+ }
+ return;
+ }
+ // At this point, we are accepting the merge.
+ t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
+ if (appearedTargets == null) return;
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.merge: calling onTasksAppeared");
+ mListener.onTasksAppeared(appearedTargets);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+ }
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ }
+
+ /** For now, just set-up a jump-cut to the new activity. */
+ private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) {
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ t.hide(change.getLeash());
+ }
+ }
+ }
+
+ @Override
+ public TaskSnapshot screenshotTask(int taskId) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.screenshotTask: taskId=%d", taskId);
+ return ActivityTaskManager.getService().takeTaskSnapshot(taskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to screenshot task", e);
+ }
+ return null;
+ }
+
+ @Override
+ public void setInputConsumerEnabled(boolean enabled) {
+ mExecutor.execute(() -> {
+ if (mFinishCB == null || !enabled) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b",
+ mFinishCB != null, enabled);
+ return;
+ }
+ // transient launches don't receive focus automatically. Since we are taking over
+ // the gesture now, take focus explicitly.
+ // This also moves recents back to top if the user gestured before a switch
+ // animation finished.
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.setInputConsumerEnabled: set focus to recents");
+ ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set focused task", e);
+ }
+ });
+ }
+
+ @Override
+ public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+ }
+
+ @Override
+ public void setFinishTaskTransaction(int taskId,
+ PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.setFinishTaskTransaction: taskId=%d", taskId);
+ mExecutor.execute(() -> {
+ if (mFinishCB == null) return;
+ mPipTransaction = finishTransaction;
+ });
+ }
+
+ @Override
+ @SuppressLint("NewApi")
+ public void finish(boolean toHome, boolean sendUserLeaveHint) {
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+ }
+
+ private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+ if (mFinishCB == null) {
+ Slog.e(TAG, "Duplicate call to finish");
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner: toHome=%b userLeaveHint=%b willFinishToHome=%b",
+ toHome, sendUserLeaveHint, mWillFinishToHome);
+ final Transitions.TransitionFinishCallback finishCB = mFinishCB;
+ mFinishCB = null;
+
+ final SurfaceControl.Transaction t = mFinishTransaction;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (mKeyguardLocked && mRecentsTask != null) {
+ if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
+ else wct.restoreTransientOrder(mRecentsTask);
+ }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
+ // The gesture is returning to the pausing-task(s) rather than continuing with
+ // recents, so end the transition by moving the app back to the top (and also
+ // re-showing it's task).
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ // reverse order so that index 0 ends up on top
+ wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
+ t.show(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " 3p launching home");
+ // Special situation where 3p launcher was changed during recents (this happens
+ // during tapltests...). Here we get both "return to home" AND "home opening".
+ // This is basically going home, but we have to restore the recents and home order.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ final TaskState state = mOpeningTasks.get(i);
+ if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // Make sure it is on top.
+ wct.reorder(state.mToken, true /* onTop */);
+ }
+ t.show(state.mTaskSurface);
+ }
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
+ // The general case: committing to recents, going home, or switching tasks.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ t.show(mOpeningTasks.get(i).mTaskSurface);
+ }
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ if (!sendUserLeaveHint) {
+ // This means recents is not *actually* finishing, so of course we gotta
+ // do special stuff in WMCore to accommodate.
+ wct.setDoNotPip(mPausingTasks.get(i).mToken);
+ }
+ // Since we will reparent out of the leashes, pre-emptively hide the child
+ // surface to match the leash. Otherwise, there will be a flicker before the
+ // visibility gets committed in Core when using split-screen (in splitscreen,
+ // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
+ t.show(mInfo.getChange(mPipTask).getLeash());
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+ mInfo.getChange(mPipTask).getLeash(), t);
+ mPipTask = null;
+ mPipTransaction = null;
+ }
+ }
+ cleanUp();
+ finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+ }
+
+ @Override
+ public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+ }
+
+ @Override
+ public void cleanupScreenshot() {
+ }
+
+ @Override
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ mExecutor.execute(() -> {
+ mWillFinishToHome = willFinishToHome;
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#removeTask
+ */
+ @Override
+ public boolean removeTask(int taskId) {
+ return false;
+ }
+
+ /**
+ * @see IRecentsAnimationController#detachNavigationBarFromApp
+ */
+ @Override
+ public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.detachNavigationBarFromApp");
+ mExecutor.execute(() -> {
+ if (mTransition == null) return;
+ try {
+ ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to detach the navigation bar from app", e);
+ }
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+ */
+ @Override
+ public void animateNavigationBarToApp(long duration) {
+ }
+ };
+
+ /** Utility class to track the state of a task as-seen by recents. */
+ private static class TaskState {
+ WindowContainerToken mToken;
+ ActivityManager.RunningTaskInfo mTaskInfo;
+
+ /** The surface/leash of the task provided by Core. */
+ SurfaceControl mTaskSurface;
+
+ /** The (local) animation-leash created for this task. */
+ SurfaceControl mLeash;
+
+ TaskState(TransitionInfo.Change change, SurfaceControl leash) {
+ mToken = change.getContainer();
+ mTaskInfo = change.getTaskInfo();
+ mTaskSurface = change.getLeash();
+ mLeash = leash;
+ }
+
+ static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken.equals(change.getContainer())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String toString() {
+ return "" + mToken + " : " + mLeash;
+ }
+ }
+
+ /**
+ * An interface for a mixed handler to receive information about recents requests (since these
+ * come into this handler directly vs from WMCore request).
+ */
+ public interface RecentsMixedHandler {
+ /**
+ * Called when a recents request comes in. The handler can add operations to outWCT. If
+ * the handler wants to "accept" the transition, it should return itself; otherwise, it
+ * should return `null`.
+ *
+ * If a mixed-handler accepts this recents, it will be the de-facto handler for this
+ * transition and is required to call the associated {@link #startAnimation},
+ * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
+ */
+ Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT);
+
+ /**
+ * Reports the transition token associated with the accepted recents request. If there was
+ * a problem starting the request, this will be called with `null`.
+ */
+ void setRecentsTransition(@Nullable IBinder transition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 81e118a31b73..f819bee2d5e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -129,9 +129,10 @@ interface ISplitScreen {
/**
* Start a pair of intents in one transition.
*/
- oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
- in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+ oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
+ in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
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 94b9e907fa76..498f95c8595e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,7 +31,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -89,7 +88,6 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -329,9 +327,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mTaskOrganizer.getRunningTaskInfo(taskId);
}
+ /** Check task is under split or not by taskId. */
public boolean isTaskInSplitScreen(int taskId) {
- return isSplitScreenVisible()
- && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ }
+
+ /** Check split is foreground and task is under split or not by taskId. */
+ public boolean isTaskInSplitScreenForeground(int taskId) {
+ return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
}
public @SplitPosition int getSplitPosition(int taskId) {
@@ -339,8 +342,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
- return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
- new WindowContainerTransaction());
+ return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
}
/**
@@ -351,13 +353,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.updateSurfaces(transaction);
}
- private boolean moveToStage(int taskId, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
}
- return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
+ return mStageCoordinator.moveToStage(task, stagePosition, wct);
}
public boolean removeFromSideStage(int taskId) {
@@ -382,10 +384,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
- final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
final int stagePosition =
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- moveToStage(taskId, stageType, stagePosition, wct);
+ moveToStage(taskId, stagePosition, wct);
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
@@ -540,6 +541,34 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ if (options1 == null) options1 = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ final String packageName1 = shortcutInfo.getPackage();
+ // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+ // recents that hasn't launched and is not being organized
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ if (mRecentTasksOptional.isPresent()) {
+ mRecentTasksOptional.get().removeSplitPair(taskId);
+ }
+ taskId = INVALID_TASK_ID;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
+ splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
/**
* See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
@@ -579,6 +608,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+ // recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2)) {
if (supportMultiInstancesSplit(packageName1)) {
@@ -586,6 +617,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
+ if (mRecentTasksOptional.isPresent()) {
+ mRecentTasksOptional.get().removeSplitPair(taskId);
+ }
+ taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
@@ -625,6 +660,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
splitPosition, splitRatio, adapter, instanceId);
}
+ private void startIntents(PendingIntent pendingIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+ final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ pendingIntent2 = null;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
+ pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
@@ -1045,9 +1109,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
float splitRatio, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
- (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
- options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
- instanceId));
+ (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
@@ -1065,11 +1128,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable Bundle options2,
+ public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
- // TODO(b/259368992): To be implemented.
+ executeRemoteCallWithTaskPermission(mController, "startIntents",
+ (controller) ->
+ controller.startIntents(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, remoteTransition, 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 e1c089550c2d..51b8000f14bb 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
@@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
@@ -65,13 +66,11 @@ class SplitScreenTransitions {
private final Transitions mTransitions;
private final Runnable mOnFinish;
- DismissTransition mPendingDismiss = null;
+ DismissSession mPendingDismiss = null;
TransitSession mPendingEnter = null;
- TransitSession mPendingRecent = null;
TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
- OneShotRemoteHandler mPendingRemoteHandler = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
@@ -100,27 +99,30 @@ class SplitScreenTransitions {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
mFinishTransaction = finishTransaction;
- if (mPendingRemoteHandler != null) {
- mPendingRemoteHandler.startAnimation(transition, info, startTransaction,
- finishTransaction, mRemoteFinishCB);
- mActiveRemoteHandler = mPendingRemoteHandler;
- mPendingRemoteHandler = null;
- return;
+
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null) {
+ if (pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
+ if (pendingTransition.mRemoteHandler != null) {
+ pendingTransition.mRemoteHandler.startAnimation(transition, info, startTransaction,
+ finishTransaction, mRemoteFinishCB);
+ mActiveRemoteHandler = pendingTransition.mRemoteHandler;
+ return;
+ }
}
+
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
- final TransitSession pendingTransition = getPendingTransition(transition);
- if (pendingTransition != null && pendingTransition.mCanceled) {
- // The pending transition was canceled, so skip playing animation.
- t.apply();
- onFinish(null /* wct */, null /* wctCB */);
- return;
- }
-
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -128,6 +130,7 @@ class SplitScreenTransitions {
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -135,24 +138,20 @@ class SplitScreenTransitions {
t.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
- t.setLayer(leash, info.getChanges().size() - i);
+ t.reparent(parentChange.getLeash(), info.getRoot(rootIdx).getLeash());
+ t.setLayer(parentChange.getLeash(), info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
mFinishTransaction.setPosition(leash,
change.getEndRelOffset().x, change.getEndRelOffset().y);
}
- // TODO(shell-transitions): screenshot here
- final Rect startBounds = new Rect(change.getStartAbsBounds());
- final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- startExampleResizeAnimation(leash, startBounds, endBounds);
}
boolean isRootOrSplitSideRoot = change.getParent() == null
|| topRoot.equals(change.getParent());
- // For enter or exit, we only want to animate the side roots but not the top-root.
- if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
+ boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+ // For enter or exit, we only want to animate side roots and the divider but not the
+ // top-root.
+ if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
continue;
}
@@ -161,6 +160,10 @@ class SplitScreenTransitions {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
+ } else if (isDivider) {
+ t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+ t.setLayer(leash, Integer.MAX_VALUE);
+ t.show(leash);
}
boolean isOpening = isOpeningTransition(info);
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
@@ -209,7 +212,7 @@ class SplitScreenTransitions {
}
}
- void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@@ -257,10 +260,6 @@ class SplitScreenTransitions {
return mPendingEnter != null && mPendingEnter.mTransition == transition;
}
- boolean isPendingRecent(IBinder transition) {
- return mPendingRecent != null && mPendingRecent.mTransition == transition;
- }
-
boolean isPendingDismiss(IBinder transition) {
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
@@ -273,8 +272,6 @@ class SplitScreenTransitions {
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
return mPendingEnter;
- } else if (isPendingRecent(transition)) {
- return mPendingRecent;
} else if (isPendingDismiss(transition)) {
return mPendingDismiss;
} else if (isPendingResize(transition)) {
@@ -284,6 +281,12 @@ class SplitScreenTransitions {
return null;
}
+ void startFullscreenTransition(WindowContainerTransaction wct,
+ @Nullable RemoteTransition handler) {
+ mTransitions.startTransition(TRANSIT_OPEN, wct,
+ new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@@ -293,6 +296,11 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback) {
+ if (mPendingEnter != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " skip to start enter split transition since it already exist. ");
+ return null;
+ }
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
@@ -303,14 +311,8 @@ class SplitScreenTransitions {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback) {
- mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
-
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mPendingRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- mPendingRemoteHandler.setTransition(transition);
- }
+ mPendingEnter = new TransitSession(
+ transition, consumedCallback, finishedCallback, remoteTransition);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -320,6 +322,12 @@ class SplitScreenTransitions {
IBinder startDismissTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
+ if (mPendingDismiss != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " skip to start dismiss split transition since it already exist. reason to "
+ + " dismiss = %s", exitReasonToString(reason));
+ return null;
+ }
final int type = reason == EXIT_REASON_DRAG_DIVIDER
? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
IBinder transition = mTransitions.startTransition(type, wct, handler);
@@ -330,7 +338,7 @@ class SplitScreenTransitions {
/** Sets a transition to dismiss split. */
void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
- mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
+ mPendingDismiss = new DismissSession(transition, reason, dismissTop);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
@@ -358,32 +366,10 @@ class SplitScreenTransitions {
+ " deduced Resize split screen");
}
- void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition,
- @Nullable TransitionFinishedCallback finishCallback) {
- mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
-
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mPendingRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- mPendingRemoteHandler.setTransition(transition);
- }
-
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
- }
-
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
- if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- mPendingRecent.setFinishedCallback(null);
- }
-
if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
@@ -411,19 +397,13 @@ class SplitScreenTransitions {
// An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
- mPendingRemoteHandler = null;
}
mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
- mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
- } else if (isPendingRecent(transition)) {
- mPendingRecent.onConsumed(aborted);
- mPendingRecent = null;
- mPendingRemoteHandler = null;
} else if (isPendingResize(transition)) {
mPendingResize.onConsumed(aborted);
mPendingResize = null;
@@ -437,9 +417,6 @@ class SplitScreenTransitions {
if (isPendingEnter(mAnimatingTransition)) {
mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- } else if (isPendingRecent(mAnimatingTransition)) {
- mPendingRecent.onFinished(wct, mFinishTransaction);
- mPendingRecent = null;
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
@@ -448,7 +425,6 @@ class SplitScreenTransitions {
mPendingResize = null;
}
- mPendingRemoteHandler = null;
mActiveRemoteHandler = null;
mAnimatingTransition = null;
@@ -554,10 +530,11 @@ class SplitScreenTransitions {
}
/** Session for a transition and its clean-up callback. */
- static class TransitSession {
+ class TransitSession {
final IBinder mTransition;
TransitionConsumedCallback mConsumedCallback;
TransitionFinishedCallback mFinishedCallback;
+ OneShotRemoteHandler mRemoteHandler;
/** Whether the transition was canceled. */
boolean mCanceled;
@@ -565,10 +542,24 @@ class SplitScreenTransitions {
TransitSession(IBinder transition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback) {
+ this(transition, consumedCallback, finishedCallback, null /* remoteTransition */);
+ }
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback,
+ @Nullable RemoteTransition remoteTransition) {
mTransition = transition;
mConsumedCallback = consumedCallback;
mFinishedCallback = finishedCallback;
+ if (remoteTransition != null) {
+ // Wrapping the remote transition for ease-of-use. (OneShot handles all the binder
+ // linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ mRemoteHandler.setTransition(transition);
+ }
}
/** Sets transition consumed callback. */
@@ -607,11 +598,11 @@ class SplitScreenTransitions {
}
/** Bundled information of dismiss transition. */
- static class DismissTransition extends TransitSession {
+ class DismissSession extends TransitSession {
final int mReason;
final @SplitScreen.StageType int mDismissTop;
- DismissTransition(IBinder transition, int reason, int dismissTop) {
+ DismissSession(IBinder transition, int reason, int dismissTop) {
super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
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 225258773013..e4f27240b2cb 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
@@ -204,9 +204,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mIsSplitEntering;
+ private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
private boolean mIsExiting;
+ private boolean mIsRootTranslucent;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -258,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- private final SplitScreenTransitions.TransitionFinishedCallback
- mRecentTransitionFinishedCallback =
- new SplitScreenTransitions.TransitionFinishedCallback() {
- @Override
- public void onFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current
- // split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT,
- false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
-
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
-
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
@@ -387,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mMainStage.isActive();
}
+ /** Checks if `transition` is a pending enter-split transition. */
+ public boolean isPendingEnter(IBinder transition) {
+ return mSplitTransitions.isPendingEnter(transition);
+ }
+
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -398,66 +373,43 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return STAGE_TYPE_UNDEFINED;
}
- boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
StageTaskListener targetStage;
int sideStagePosition;
- if (stageType == STAGE_TYPE_MAIN) {
- targetStage = mMainStage;
- sideStagePosition = reverseSplitPosition(stagePosition);
- } else if (stageType == STAGE_TYPE_SIDE) {
+ if (isSplitScreenVisible()) {
+ // If the split screen is foreground, retrieves target stage based on position.
+ targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+ sideStagePosition = mSideStagePosition;
+ } else {
targetStage = mSideStage;
sideStagePosition = stagePosition;
- } else {
- if (isSplitScreenVisible()) {
- // If the split screen is activated, retrieves target stage based on position.
- targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
- sideStagePosition = mSideStagePosition;
- } else {
- // Exit split if it running background.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-
- targetStage = mSideStage;
- sideStagePosition = stagePosition;
- }
}
- setSideStagePosition(sideStagePosition, wct);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- targetStage.evictAllChildren(evictWct);
-
- // Apply surface bounds before animation start.
- SurfaceControl.Transaction startT = mTransactionPool.acquire();
- if (startT != null) {
- updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
- startT.apply();
- mTransactionPool.release(startT);
- }
- // reparent the task to an invisible split root will make the activity invisible. Reorder
- // the root task to front to make the entering transition from pip to split smooth.
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
- wct.reorder(targetStage.mRootTaskInfo.token, true);
- wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true);
- // prevent the fling divider to center transition
- mIsDropEntering = true;
-
- targetStage.addTask(task, wct);
-
- if (ENABLE_SHELL_TRANSITIONS) {
- prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, null /* consumedCallback */, (finishWct, finishT) -> {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
- } /* finishedCallback */);
+ if (!isSplitActive()) {
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct, task, stagePosition);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
} else {
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
+ setSideStagePosition(sideStagePosition, wct);
+ targetStage.addTask(task, wct);
+ targetStage.evictAllChildren(wct);
+ if (!isSplitScreenVisible()) {
+ final StageTaskListener anotherStage = targetStage == mMainStage
+ ? mSideStage : mMainStage;
+ anotherStage.reparentTopTask(wct);
+ anotherStage.evictAllChildren(wct);
+ wct.reorder(mRootTaskInfo.token, true);
}
- mTaskOrganizer.applyTransaction(wct);
+ setRootForceTranslucent(false, wct);
+ mSyncQueue.queue(wct);
}
+
+ // Due to drag already pip task entering split by this method so need to reset flag here.
+ mIsDropEntering = false;
return true;
}
@@ -660,6 +612,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId2 == INVALID_TASK_ID) {
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ if (mRecentTasks.isPresent()) {
+ mRecentTasks.get().removeSplitPair(taskId1);
+ }
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.startTask(taskId1, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -675,6 +640,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId == INVALID_TASK_ID) {
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -689,6 +661,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId == INVALID_TASK_ID) {
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -716,7 +695,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -730,6 +709,57 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setEnterInstanceId(instanceId);
}
+ void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (pendingIntent2 == null) {
+ options1 = options1 != null ? options1 : new Bundle();
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
+ if (!mMainStage.isActive()) {
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(wct, false /* reparent */);
+ }
+
+ prepareEvictChildTasksIfSplitActive(wct);
+ mSplitLayout.setDivideRatio(splitRatio);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ setRootForceTranslucent(false, wct);
+
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ options2 = options2 != null ? options2 : new Bundle();
+ addActivityOptions(options2, mMainStage);
+ if (shortcutInfo2 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
+ } else {
+ wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
+ }
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
+ setEnterInstanceId(instanceId);
+ }
+
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -738,6 +768,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (options1 == null) options1 = new Bundle();
if (taskId2 == INVALID_TASK_ID) {
// Launching a solo task.
+ // Exit split first if this task under split roots.
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
options1 = activityOptions.toBundle();
@@ -764,17 +798,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- }
- mSyncQueue.queue(wct);
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
return;
}
@@ -797,13 +823,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
return;
}
@@ -822,13 +844,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
return;
}
@@ -838,6 +855,49 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
@@ -881,7 +941,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
- mIsSplitEntering = true;
+ mIsDividerRemoteAnimating = true;
if (mSplitRequest == null) {
mSplitRequest = new SplitRequest(mainTaskId,
mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
@@ -894,27 +954,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (options == null) options = new Bundle();
addActivityOptions(options, mMainStage);
- options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ setRootForceTranslucent(false, wct);
// TODO(b/268008375): Merge APIs to start a split pair into one.
if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
wct.startTask(mainTaskId, options);
- } else if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ mSyncQueue.queue(wct);
} else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
}
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
-
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
-
setEnterInstanceId(instanceId);
}
@@ -953,6 +1011,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
onRemoteAnimationFinishedOrCancelled(evictWct);
+ setDividerVisibility(true, null);
try {
adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
@@ -967,6 +1026,55 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return activityOptions.toBundle();
}
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ setDividerVisibility(true, t);
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ return transition;
+ }
+
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
@@ -974,7 +1082,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
mSplitRequest = null;
// If any stage has no child after animation finished, it means that split will display
@@ -993,6 +1101,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -1247,14 +1376,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
mShouldUpdateRecents = false;
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1286,7 +1415,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, finishedWCT);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1408,12 +1537,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
mSplitLayout.init();
setDividerVisibility(true, t);
+ // Ensure divider surface are re-parented back into the hierarchy at the end of the
+ // transition. See Transition#buildFinishTransaction for more detail.
+ t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
t.show(mRootTaskLeash);
setSplitsVisible(true);
@@ -1590,7 +1723,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.update(null /* t */);
onLayoutSizeChanged(mSplitLayout);
}
@@ -1612,6 +1745,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = null;
mRootTaskLeash = null;
+ mIsRootTranslucent = false;
}
@@ -1630,7 +1764,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(wct);
@@ -1640,9 +1774,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ // Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
- && !mIsSplitEntering) {
- // Handle entring split case here if split already running background.
+ && mSplitRequest == null) {
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
} else {
@@ -1654,7 +1788,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictOtherChildren(wct, taskId);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1678,6 +1812,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
+ private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) {
+ if (mIsRootTranslucent == translucent) return;
+
+ mIsRootTranslucent = translucent;
+ wct.setForceTranslucent(mRootTaskInfo.token, translucent);
+ }
+
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
// If split didn't active, just ignore this callback because we should already did these
// on #applyExitSplitScreen.
@@ -1704,16 +1845,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
setDividerVisibility(mainStageVisible, null);
}
+ // Set divider visibility flag and try to apply it, the param transaction is used to apply.
+ // See applyDividerVisibility for more detail.
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
if (visible == mDividerVisible) {
return;
@@ -1734,27 +1878,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerVisible = visible;
sendSplitVisibilityChanged();
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
}
- if (t != null) {
- applyDividerVisibility(t);
- } else {
- mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
- }
+ applyDividerVisibility(t);
}
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ // Apply divider visibility by current visibility flag. If param transaction is non-null, it
+ // will apply by that transaction, if it is null and visible, it will run a fade-in animation,
+ // otherwise hide immediately.
+ private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to divider leash not ready.");
return;
}
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
@@ -1764,7 +1907,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerFadeInAnimator.cancel();
}
- if (mDividerVisible) {
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ if (t != null) {
+ t.setVisibility(dividerLeash, mDividerVisible);
+ t.setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
+ } else if (mDividerVisible) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
mDividerFadeInAnimator.addUpdateListener(animation -> {
@@ -1804,7 +1952,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerFadeInAnimator.start();
} else {
- t.hide(dividerLeash);
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ transaction.hide(dividerLeash);
+ transaction.apply();
+ mTransactionPool.release(transaction);
}
}
@@ -1822,7 +1973,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (!isSplitScreenVisible() && !mIsSplitEntering) {
+ } else if (!isSplitScreenVisible() && mSplitRequest == null) {
+ // Dismiss split screen in the background once any sides of the split become empty.
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
@@ -1838,7 +1990,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
@@ -2166,11 +2318,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME
- || activityType == ACTIVITY_TYPE_RECENTS) {
- // Enter overview panel, so start recent transition.
- mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionFinishedCallback);
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ // starting recents/home, so don't handle this and let it fall-through to
+ // the remote handler.
+ return null;
}
}
} else {
@@ -2263,9 +2414,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- // TODO(shell-transitions): Implement a fallback behavior for now.
- throw new IllegalStateException("Somehow removed the last task in a stage"
- + " outside of a proper transition");
+ Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ + "transition.");
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final int dismissTop = mMainStage.getChildCount() == 0
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(dismissTop, wct);
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop,
+ EXIT_REASON_UNKNOWN);
// This can happen in some pathological cases. For example:
// 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
// 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
@@ -2299,8 +2455,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(
transition, info, startTransaction, finishTransaction);
- } else if (mSplitTransitions.isPendingRecent(transition)) {
- shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2312,7 +2466,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
} else if (mSplitTransitions.isPendingResize(transition)) {
- mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+ mSplitTransitions.playResizeAnimation(transition, info, startTransaction,
finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
mSideStage.getSplitDecorManager());
@@ -2390,7 +2544,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
finishEnterSplitScreen(finishT);
- addDividerBarToTransition(info, finishT, true /* show */);
+ addDividerBarToTransition(info, true /* show */);
return true;
}
@@ -2525,7 +2679,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private boolean startPendingDismissAnimation(
- @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ @NonNull SplitScreenTransitions.DismissSession dismissTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
@@ -2533,7 +2687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false, t);
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
@@ -2551,21 +2705,52 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
}
- addDividerBarToTransition(info, finishT, false /* show */);
+ addDividerBarToTransition(info, false /* show */);
return true;
}
- private boolean startPendingRecentAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ /** Call this when starting the open-recents animation while split-screen is active. */
+ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
setDividerVisibility(false, t);
- return true;
}
- private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction finishT, boolean show) {
+ /** Call this when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT, TransitionInfo info) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // TODO(b/275664132): Remove dismissing split screen here to fit in back-to-split support.
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null && TransitionUtil.isClosingType(change.getMode())) {
+ finishT.setCrop(change.getLeash(), null).hide(change.getLeash());
+ }
+ }
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
mSplitLayout.getRefDividerBounds(mTempRect1);
+ barChange.setParent(mRootTaskInfo.token);
barChange.setStartAbsBounds(mTempRect1);
barChange.setEndAbsBounds(mTempRect1);
barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
@@ -2573,15 +2758,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Technically this should be order-0, but this is running after layer assignment
// and it's a special case, so just add to end.
info.addChange(barChange);
-
- if (show) {
- finishT.setLayer(leash, Integer.MAX_VALUE);
- finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
- finishT.show(leash);
- // Ensure divider surface are re-parented back into the hierarchy at the end of the
- // transition. See Transition#buildFinishTransaction for more detail.
- finishT.reparent(leash, mRootTaskLeash);
- }
}
RemoteAnimationTarget getDividerBarLegacyTarget() {
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 a841b7f96d3c..ead0bcd15c73 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
@@ -220,12 +220,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mCallbacks.onNoLongerSupportMultiWindow();
return;
}
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ if (taskInfo.topActivity == null && mChildrenTaskInfo.contains(taskInfo.taskId)
+ && mChildrenTaskInfo.get(taskInfo.taskId).topActivity != null) {
+ // If top activity become null, it means the task is about to vanish, we use this
+ // signal to remove it from children list earlier for smooth dismiss transition.
+ mChildrenTaskInfo.remove(taskInfo.taskId);
+ mChildrenLeashes.remove(taskInfo.taskId);
+ } else {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ }
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
- if (!ENABLE_SHELL_TRANSITIONS) {
- updateChildTaskSurface(
- taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ if (!ENABLE_SHELL_TRANSITIONS && mChildrenLeashes.contains(taskInfo.taskId)) {
+ updateChildTaskSurface(taskInfo, mChildrenLeashes.get(taskInfo.taskId),
+ false /* firstAppeared */);
}
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -259,9 +267,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
new file mode 100644
index 000000000000..1ddd8f9a3a14
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+// abstract class to create splash screen window(or windowless window)
+abstract class AbsSplashWindowCreator {
+ protected static final String TAG = StartingWindowController.TAG;
+ protected final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ protected final Context mContext;
+ protected final DisplayManager mDisplayManager;
+ protected final ShellExecutor mSplashScreenExecutor;
+ protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager;
+
+ private StartingSurface.SysuiProxy mSysuiProxy;
+
+ AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mSplashscreenContentDrawer = contentDrawer;
+ mContext = context;
+ mSplashScreenExecutor = splashScreenExecutor;
+ mDisplayManager = displayManager;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ return splashScreenThemeResId != 0
+ ? splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ }
+
+ protected Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
+ }
+
+ void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
+ mSysuiProxy = sysuiProxy;
+ }
+
+ protected void requestTopUi(boolean requestTopUi) {
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(requestTopUi, TAG);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
new file mode 100644
index 000000000000..20c4d5ae5f58
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+class SnapshotWindowCreator {
+ private final ShellExecutor mMainExecutor;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+
+ SnapshotWindowCreator(ShellExecutor mainExecutor,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mMainExecutor = mainExecutor;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ final int taskId = startingWindowInfo.taskInfo.taskId;
+ // Remove any existing starting window for this task before adding.
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
+ startingWindowInfo.appToken, snapshot, mMainExecutor,
+ () -> mStartingWindowRecordManager.removeWindow(taskId, true));
+ if (surface != null) {
+ final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
+ startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+ }
+
+ private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private final TaskSnapshotWindow mTaskSnapshotWindow;
+
+ SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
+ int activityType, ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mTaskSnapshotWindow = taskSnapshotWindow;
+ mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ mTaskSnapshotWindow.removeImmediately();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mTaskSnapshotWindow.hasImeSurface();
+ }
+ }
+}
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 ebb957b2201b..dc91a11dc64f 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
@@ -24,9 +24,6 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLA
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
-
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -96,6 +93,25 @@ import java.util.function.UnaryOperator;
public class SplashscreenContentDrawer {
private static final String TAG = StartingWindowController.TAG;
+ /**
+ * The minimum duration during which the splash screen is shown when the splash screen icon is
+ * animated.
+ */
+ static final long MINIMAL_ANIMATION_DURATION = 400L;
+
+ /**
+ * Allow the icon style splash screen to be displayed for longer to give time for the animation
+ * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
+ * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
+ */
+ static final long TIME_WINDOW_DURATION = 100L;
+
+ /**
+ * The maximum duration during which the splash screen will be shown if the application is ready
+ * to show before the icon animation finishes.
+ */
+ static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+
// The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
// icon which it's non-transparent foreground area is similar to it's background area, then
// do not enlarge the foreground drawable.
@@ -368,10 +384,10 @@ public class SplashscreenContentDrawer {
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
- themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
- if (themeBGTester.passFilterRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
+ themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
+ if (themeBGTester.passFilterRatio() != 1) {
+ // the window background is translucent, unable to draw
+ Slog.w(TAG, "Window background is translucent, fill background with black color");
return getSystemBGColor();
} else {
return themeBGTester.getDominateColor();
@@ -854,7 +870,7 @@ public class SplashscreenContentDrawer {
@Override
public float passFilterRatio() {
final int alpha = mColorDrawable.getAlpha();
- return (float) (alpha / 255);
+ return alpha / 255.0f;
}
@Override
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
new file mode 100644
index 000000000000..8a4d4c21194a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -0,0 +1,508 @@
+/*
+ * 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.startingsurface;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.internal.R;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.function.Supplier;
+
+/**
+ * A class which able to draw splash screen as the starting window for a task.
+ *
+ * In order to speed up, there will use two threads to creating a splash screen in parallel.
+ * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
+ * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
+ * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
+ * can synchronize on each frame.
+ *
+ * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
+ * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
+ * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
+ * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
+ * quickly.
+ *
+ * So basically we are using the spare time to prepare the SplashScreenView while splash screen
+ * thread is waiting for
+ * 1. WindowManager#addView(binder call to WM),
+ * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
+ * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
+ * always happen before #draw).
+ * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
+ * splash-screen background tread can make they execute in parallel, which ensure it is faster then
+ * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
+ *
+ * Here is the sequence to compare the difference between using single and two thread.
+ *
+ * Single thread:
+ * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
+ * -> draw -> AdaptiveIconDrawable#draw
+ *
+ * Two threads:
+ * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
+ * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
+ * directly).
+ */
+class SplashscreenWindowCreator extends AbsSplashWindowCreator {
+ private static final int LIGHT_BARS_MASK =
+ WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+ | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+ private final WindowManagerGlobal mWindowManagerGlobal;
+ private Choreographer mChoreographer;
+
+ /**
+ * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+ * rendered and that have not yet been removed by their client.
+ */
+ private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+ new SparseArray<>(1);
+
+ SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+ mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+ // replace with the default theme if the application didn't set
+ final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
+ if (context == null) {
+ return;
+ }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
+
+ // TODO(b/173975965) tracking performance
+ // Prepare the splash screen content view on splash screen worker thread in parallel, so the
+ // content view won't be blocked by binder call like addWindow and relayout.
+ // 1. Trigger splash screen worker thread to create SplashScreenView before/while
+ // Session#addWindow.
+ // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
+ // traversal, which will call Session#relayout on splash screen thread.
+ // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
+ // the same time the splash screen thread should be executing Session#relayout. Blocking the
+ // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
+
+ // Record whether create splash screen view success, notify to current thread after
+ // create splash screen view finished.
+ final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
+ rootLayout.setPadding(0, 0, 0, 0);
+ rootLayout.setFitsSystemWindows(false);
+ final Runnable setViewSynchronized = () -> {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
+ // waiting for setContentView before relayoutWindow
+ SplashScreenView contentView = viewSupplier.get();
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
+ ? (SplashWindowRecord) sRecord : null;
+ // If record == null, either the starting window added fail or removed already.
+ // Do not add this view if the token is mismatch.
+ if (record != null && windowInfo.appToken == record.mAppToken) {
+ // if view == null then creation of content view was failed.
+ if (contentView != null) {
+ try {
+ rootLayout.addView(contentView);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "failed set content view to starting window "
+ + "at taskId: " + taskId, e);
+ contentView = null;
+ }
+ }
+ record.setSplashScreenView(contentView);
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ };
+ requestTopUi(true);
+ mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
+ try {
+ if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
+ // We use the splash screen worker thread to create SplashScreenView while adding
+ // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
+ // And since Choreographer#doFrame won't happen immediately after adding the window,
+ // if the view is not added to the PhoneWindow on the first #doFrame, the view will
+ // not be rendered on the first frame. So here we need to synchronize the view on
+ // the window before first round relayoutWindow, which will happen after insets
+ // animation.
+ mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
+ final SplashWindowRecord record =
+ (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId);
+ if (record != null) {
+ record.parseAppSystemBarColor(context);
+ // Block until we get the background color.
+ final SplashScreenView contentView = viewSupplier.get();
+ if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ contentView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ final int lightBarAppearance =
+ ContrastColorUtil.isColorLight(
+ contentView.getInitBackgroundColor())
+ ? LIGHT_BARS_MASK : 0;
+ contentView.getWindowInsetsController()
+ .setSystemBarsAppearance(
+ lightBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+ }
+ } else {
+ // release the icon view host
+ final SplashScreenView contentView = viewSupplier.get();
+ if (contentView.getSurfaceHost() != null) {
+ SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
+ }
+ }
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
+ }
+ }
+
+ int estimateTaskBackgroundColor(TaskInfo taskInfo) {
+ if (taskInfo.topActivityInfo == null) {
+ return Color.TRANSPARENT;
+ }
+ final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+ final String packageName = activityInfo.packageName;
+ final int userId = taskInfo.userId;
+ final Context windowContext;
+ try {
+ windowContext = mContext.createPackageContextAsUser(
+ packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + taskInfo.userId, e);
+ return Color.TRANSPARENT;
+ }
+ try {
+ final IPackageManager packageManager = ActivityThread.getPackageManager();
+ final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
+ userId);
+ final int splashScreenThemeId = splashScreenThemeName != null
+ ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
+ : 0;
+
+ final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
+
+ if (theme != windowContext.getThemeResId()) {
+ windowContext.setTheme(theme);
+ }
+ return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.w(TAG, "failed get starting window background color at taskId: "
+ + taskInfo.taskId, e);
+ }
+ return Color.TRANSPARENT;
+ }
+
+ /**
+ * Called when the Task wants to copy the splash screen.
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord record =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord preView = record instanceof SplashWindowRecord
+ ? (SplashWindowRecord) record : null;
+ SplashScreenView.SplashScreenViewParcelable parcelable;
+ SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null;
+ if (splashScreenView != null && splashScreenView.isCopyable()) {
+ parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView);
+ parcelable.setClientCallback(
+ new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+ () -> onAppSplashScreenViewRemoved(taskId, false))));
+ splashScreenView.onCopied();
+ mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
+ } else {
+ parcelable = null;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Copying splash screen window view for task: %d with parcelable %b",
+ taskId, parcelable != null);
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
+
+ /**
+ * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
+ * or when the Activity is clean up.
+ *
+ * @param taskId The Task id on which the splash screen was attached
+ */
+ public void onAppSplashScreenViewRemoved(int taskId) {
+ onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ }
+
+ /**
+ * @param fromServer If true, this means the removal was notified by the server. This is only
+ * used for debugging purposes.
+ * @see #onAppSplashScreenViewRemoved(int)
+ */
+ private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+ SurfaceControlViewHost viewHost =
+ mAnimatedSplashScreenSurfaceHosts.get(taskId);
+ if (viewHost == null) {
+ return;
+ }
+ mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
+ fromServer ? "Server cleaned up" : "App removed", taskId);
+ SplashScreenView.releaseIconHost(viewHost);
+ }
+
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ boolean shouldSaveView = true;
+ final Context context = view.getContext();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
+ mWindowManagerGlobal.addView(view, params, display,
+ null /* parentWindow */, context.getUserId());
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ mWindowManagerGlobal.removeView(view, true /* immediate */);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ return shouldSaveView;
+ }
+
+ private void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final SplashWindowRecord tView =
+ new SplashWindowRecord(appToken, view, suggestType);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+
+ private void removeWindowInner(View decorView, boolean hideView) {
+ requestTopUi(false);
+ if (hideView) {
+ decorView.setVisibility(View.GONE);
+ }
+ mWindowManagerGlobal.removeView(decorView, false /* immediate */);
+ }
+
+ private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
+ private SplashScreenView mView;
+ private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
+ void setView(SplashScreenView view) {
+ synchronized (this) {
+ mView = view;
+ mIsViewSet = true;
+ notify();
+ }
+ }
+
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
+ @Override
+ @Nullable
+ public SplashScreenView get() {
+ synchronized (this) {
+ while (!mIsViewSet) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
+ return mView;
+ }
+ }
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private final IBinder mAppToken;
+ private final View mRootView;
+ @StartingWindowInfo.StartingWindowType private final int mSuggestType;
+ private final long mCreateTime;
+
+ private boolean mSetSplashScreen;
+ private SplashScreenView mSplashView;
+ private int mSystemBarAppearance;
+ private boolean mDrawsSystemBarBackgrounds;
+
+ SplashWindowRecord(IBinder appToken, View decorView,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ mAppToken = appToken;
+ mRootView = decorView;
+ mSuggestType = suggestType;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ void setSplashScreenView(@Nullable SplashScreenView splashScreenView) {
+ if (mSetSplashScreen) {
+ return;
+ }
+ mSplashView = splashScreenView;
+ mBGColor = mSplashView != null ? mSplashView.getInitBackgroundColor()
+ : Color.TRANSPARENT;
+ mSetSplashScreen = true;
+ }
+
+ void parseAppSystemBarColor(Context context) {
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ mDrawsSystemBarBackgrounds = a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
+ if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+ }
+ if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+ }
+ a.recycle();
+ }
+
+ // Reset the system bar color which set by splash screen, make it align to the app.
+ void clearSystemBarColor() {
+ if (mRootView == null || !mRootView.isAttachedToWindow()) {
+ return;
+ }
+ if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
+ final WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mRootView.getLayoutParams();
+ if (mDrawsSystemBarBackgrounds) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ } else {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ mRootView.setLayoutParams(lp);
+ }
+ mRootView.getWindowInsetsController().setSystemBarsAppearance(
+ mSystemBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (mRootView == null) {
+ return;
+ }
+ if (mSplashView == null) {
+ // shouldn't happen, the app window may be drawn earlier than starting window?
+ Slog.e(TAG, "Found empty splash screen, remove!");
+ removeWindowInner(mRootView, false);
+ return;
+ }
+ clearSystemBarColor();
+ if (immediately
+ || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ removeWindowInner(mRootView, false);
+ } else {
+ if (info.playRevealAnimation) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ () -> removeWindowInner(mRootView, true),
+ mCreateTime, info.roundedCornerRadius);
+ } else {
+ // the SplashScreenView has been copied to client, hide the view to skip
+ // default exit animation
+ removeWindowInner(mRootView, true);
+ }
+ }
+ }
+ }
+}
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 4f07bfeacce5..ff06db370d1a 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
@@ -16,169 +16,80 @@
package com.android.wm.shell.startingsurface;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
+import android.annotation.CallSuper;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
+import android.content.res.Configuration;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Slog;
import android.util.SparseArray;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsetsController;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.widget.FrameLayout;
+import android.view.WindowlessWindowManager;
import android.window.SplashScreenView;
-import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ContrastColorUtil;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.function.Supplier;
-
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
- *
- * In order to speed up, there will use two threads to creating a splash screen in parallel.
- * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
- * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
- * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
- * can synchronize on each frame.
- *
- * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
- * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
- * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
- * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
- * quickly.
- *
- * So basically we are using the spare time to prepare the SplashScreenView while splash screen
- * thread is waiting for
- * 1. WindowManager#addView(binder call to WM),
- * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
- * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
- * always happen before #draw).
- * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
- * splash-screen background tread can make they execute in parallel, which ensure it is faster then
- * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
- *
- * Here is the sequence to compare the difference between using single and two thread.
- *
- * Single thread:
- * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
- * -> draw -> AdaptiveIconDrawable#draw
- *
- * Two threads:
- * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
- * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
- * directly).
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
- private static final String TAG = StartingWindowController.TAG;
- private final Context mContext;
- private final DisplayManager mDisplayManager;
private final ShellExecutor mSplashScreenExecutor;
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
- private Choreographer mChoreographer;
- private final WindowManagerGlobal mWindowManagerGlobal;
- private StartingSurface.SysuiProxy mSysuiProxy;
- private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
-
- private static final int LIGHT_BARS_MASK =
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- /**
- * The minimum duration during which the splash screen is shown when the splash screen icon is
- * animated.
- */
- static final long MINIMAL_ANIMATION_DURATION = 400L;
-
- /**
- * Allow the icon style splash screen to be displayed for longer to give time for the animation
- * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
- * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
- */
- static final long TIME_WINDOW_DURATION = 100L;
-
- /**
- * The maximum duration during which the splash screen will be shown if the application is ready
- * to show before the icon animation finishes.
- */
- static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+ @VisibleForTesting
+ final SplashscreenWindowCreator mSplashscreenWindowCreator;
+ private final SnapshotWindowCreator mSnapshotWindowCreator;
+ private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
+ private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
+ // Windowless surface could co-exist with starting window in a task.
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
IconProvider iconProvider, TransactionPool pool) {
- mContext = context;
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
- mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
- mWindowManagerGlobal = WindowManagerGlobal.getInstance();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
-
- @VisibleForTesting
- final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
-
- /**
- * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
- * rendered and that have not yet been removed by their client.
- */
- private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
- new SparseArray<>(1);
-
- private Display getDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId);
- }
-
- int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
- return splashScreenThemeResId != 0
- ? splashScreenThemeResId
- : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
- : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
+ displayManager.getDisplay(DEFAULT_DISPLAY);
+
+ mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
+ context, splashScreenExecutor, displayManager, mWindowRecords);
+ mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
+ mWindowRecords);
+ mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
+ mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
+ mWindowlessRecords, pool);
+ mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
+ mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
}
void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
- mSysuiProxy = sysuiProxy;
+ mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
+ mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
}
/**
@@ -186,231 +97,55 @@ public class StartingSurfaceDrawer {
*
* @param suggestType The suggestion type to draw the splash screen.
*/
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
- final RunningTaskInfo taskInfo = windowInfo.taskInfo;
- final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
- ? windowInfo.targetActivityInfo
- : taskInfo.topActivityInfo;
- if (activityInfo == null || activityInfo.packageName == null) {
- return;
- }
- // replace with the default theme if the application didn't set
- final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
- suggestType, mDisplayManager);
- if (context == null) {
- return;
- }
- final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
- context, windowInfo, suggestType, activityInfo.packageName,
- suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
- final Display display = getDisplay(displayId);
-
- // TODO(b/173975965) tracking performance
- // Prepare the splash screen content view on splash screen worker thread in parallel, so the
- // content view won't be blocked by binder call like addWindow and relayout.
- // 1. Trigger splash screen worker thread to create SplashScreenView before/while
- // Session#addWindow.
- // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
- // traversal, which will call Session#relayout on splash screen thread.
- // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
- // the same time the splash screen thread should be executing Session#relayout. Blocking the
- // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
-
- // Record whether create splash screen view success, notify to current thread after
- // create splash screen view finished.
- final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(context));
- rootLayout.setPadding(0, 0, 0, 0);
- rootLayout.setFitsSystemWindows(false);
- final Runnable setViewSynchronized = () -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
- // waiting for setContentView before relayoutWindow
- SplashScreenView contentView = viewSupplier.get();
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- // If record == null, either the starting window added fail or removed already.
- // Do not add this view if the token is mismatch.
- if (record != null && appToken == record.mAppToken) {
- // if view == null then creation of content view was failed.
- if (contentView != null) {
- try {
- rootLayout.addView(contentView);
- } catch (RuntimeException e) {
- Slog.w(TAG, "failed set content view to starting window "
- + "at taskId: " + taskId, e);
- contentView = null;
- }
- }
- record.setSplashScreenView(contentView);
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- };
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(true, TAG);
- }
- mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
- viewSupplier::setView, viewSupplier::setUiThreadInitTask);
- try {
- if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
- // We use the splash screen worker thread to create SplashScreenView while adding
- // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
- // And since Choreographer#doFrame won't happen immediately after adding the window,
- // if the view is not added to the PhoneWindow on the first #doFrame, the view will
- // not be rendered on the first frame. So here we need to synchronize the view on
- // the window before first round relayoutWindow, which will happen after insets
- // animation.
- mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- record.parseAppSystemBarColor(context);
- // Block until we get the background color.
- final SplashScreenView contentView = viewSupplier.get();
- if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- contentView.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- final int lightBarAppearance = ContrastColorUtil.isColorLight(
- contentView.getInitBackgroundColor())
- ? LIGHT_BARS_MASK : 0;
- contentView.getWindowInsetsController().setSystemBarsAppearance(
- lightBarAppearance, LIGHT_BARS_MASK);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
- }
- record.mBGColor = contentView.getInitBackgroundColor();
- } else {
- // release the icon view host
- final SplashScreenView contentView = viewSupplier.get();
- if (contentView.getSurfaceHost() != null) {
- SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
- }
- }
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
- }
+ mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
int getStartingWindowBackgroundColorForTask(int taskId) {
- final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
+ final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
if (startingWindowRecord == null) {
return Color.TRANSPARENT;
}
- return startingWindowRecord.mBGColor;
- }
-
- private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
- private SplashScreenView mView;
- private boolean mIsViewSet;
- private Runnable mUiThreadInitTask;
- void setView(SplashScreenView view) {
- synchronized (this) {
- mView = view;
- mIsViewSet = true;
- notify();
- }
- }
-
- void setUiThreadInitTask(Runnable initTask) {
- synchronized (this) {
- mUiThreadInitTask = initTask;
- }
- }
-
- @Override
- @Nullable
- public SplashScreenView get() {
- synchronized (this) {
- while (!mIsViewSet) {
- try {
- wait();
- } catch (InterruptedException ignored) {
- }
- }
- if (mUiThreadInitTask != null) {
- mUiThreadInitTask.run();
- mUiThreadInitTask = null;
- }
- return mView;
- }
- }
+ return startingWindowRecord.getBGColor();
}
int estimateTaskBackgroundColor(TaskInfo taskInfo) {
- if (taskInfo.topActivityInfo == null) {
- return Color.TRANSPARENT;
- }
- final ActivityInfo activityInfo = taskInfo.topActivityInfo;
- final String packageName = activityInfo.packageName;
- final int userId = taskInfo.userId;
- final Context windowContext;
- try {
- windowContext = mContext.createPackageContextAsUser(
- packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + packageName + " for user " + taskInfo.userId, e);
- return Color.TRANSPARENT;
- }
- try {
- final IPackageManager packageManager = ActivityThread.getPackageManager();
- final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
- userId);
- final int splashScreenThemeId = splashScreenThemeName != null
- ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
- : 0;
-
- final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
-
- if (theme != windowContext.getThemeResId()) {
- windowContext.setTheme(theme);
- }
- return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
- } catch (RuntimeException | RemoteException e) {
- Slog.w(TAG, "failed get starting window background color at taskId: "
- + taskInfo.taskId, e);
- }
- return Color.TRANSPARENT;
+ return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
}
/**
* Called when a task need a snapshot starting window.
*/
- void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
- TaskSnapshot snapshot) {
- final int taskId = startingWindowInfo.taskInfo.taskId;
- // Remove any existing starting window for this task before adding.
- removeWindowNoAnimate(taskId);
- final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
- snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
- if (surface == null) {
- return;
- }
- final StartingWindowRecord tView = new StartingWindowRecord(appToken,
- null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
- mStartingWindowRecords.put(taskId, tView);
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Task start finish, remove starting surface for task: %d",
- removalInfo.taskId);
- removeWindowSynced(removalInfo, false /* immediately */);
+ if (removalInfo.windowlessSurface) {
+ mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Task start finish, remove starting surface for task: %d",
+ removalInfo.taskId);
+ mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ }
+ }
+
+ /**
+ * Create a windowless starting surface and attach to the root surface.
+ */
+ void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
+ if (windowInfo.taskSnapshot != null) {
+ mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
+ windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
+ } else {
+ mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
+ windowInfo, windowInfo.rootSurface);
+ }
}
/**
@@ -419,37 +154,15 @@ public class StartingSurfaceDrawer {
public void clearAllWindows() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Clear all starting windows immediately");
- final int taskSize = mStartingWindowRecords.size();
- final int[] taskIds = new int[taskSize];
- for (int i = taskSize - 1; i >= 0; --i) {
- taskIds[i] = mStartingWindowRecords.keyAt(i);
- }
- for (int i = taskSize - 1; i >= 0; --i) {
- removeWindowNoAnimate(taskIds[i]);
- }
+ mWindowRecords.clearAllWindows();
+ mWindowlessRecords.clearAllWindows();
}
/**
* Called when the Task wants to copy the splash screen.
*/
public void copySplashScreenView(int taskId) {
- final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
- SplashScreenViewParcelable parcelable;
- SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
- if (splashScreenView != null && splashScreenView.isCopyable()) {
- parcelable = new SplashScreenViewParcelable(splashScreenView);
- parcelable.setClientCallback(
- new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
- () -> onAppSplashScreenViewRemoved(taskId, false))));
- splashScreenView.onCopied();
- mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
- } else {
- parcelable = null;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Copying splash screen window view for task: %d with parcelable %b",
- taskId, parcelable != null);
- ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ mSplashscreenWindowCreator.copySplashScreenView(taskId);
}
/**
@@ -459,195 +172,148 @@ public class StartingSurfaceDrawer {
* @param taskId The Task id on which the splash screen was attached
*/
public void onAppSplashScreenViewRemoved(int taskId) {
- onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
}
- /**
- * @param fromServer If true, this means the removal was notified by the server. This is only
- * used for debugging purposes.
- * @see #onAppSplashScreenViewRemoved(int)
- */
- private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
- SurfaceControlViewHost viewHost =
- mAnimatedSplashScreenSurfaceHosts.get(taskId);
- if (viewHost == null) {
- return;
+ void onImeDrawnOnTask(int taskId) {
+ onImeDrawnOnTask(mWindowRecords, taskId);
+ onImeDrawnOnTask(mWindowlessRecords, taskId);
+ }
+
+ private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ records.getRecord(taskId);
+ final SnapshotRecord record = sRecord instanceof SnapshotRecord
+ ? (SnapshotRecord) sRecord : null;
+ if (record != null && record.hasImeSurface()) {
+ records.removeWindow(taskId, true);
}
- mAnimatedSplashScreenSurfaceHosts.remove(taskId);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
- fromServer ? "Server cleaned up" : "App removed", taskId);
- SplashScreenView.releaseIconHost(viewHost);
}
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
- boolean shouldSaveView = true;
- final Context context = view.getContext();
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
- mWindowManagerGlobal.addView(view, params, display,
- null /* parentWindow */, context.getUserId());
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- mWindowManagerGlobal.removeView(view, true /* immediate */);
- shouldSaveView = false;
+ static class WindowlessStartingWindow extends WindowlessWindowManager {
+ SurfaceControl mChildSurface;
+
+ WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
+ super(c, rootSurface, null /* hostInputToken */);
+ }
+
+ @Override
+ protected SurfaceControl getParentSurface(IWindow window,
+ WindowManager.LayoutParams attrs) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("Windowless window")
+ .setHidden(false)
+ .setParent(mRootSurface)
+ .setCallsite("WindowlessStartingWindow#attachToParentSurface");
+ mChildSurface = builder.build();
+ try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+ t.setLayer(mChildSurface, Integer.MAX_VALUE);
+ t.apply();
}
+ return mChildSurface;
}
- if (shouldSaveView) {
- removeWindowNoAnimate(taskId);
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ abstract static class StartingWindowRecord {
+ protected int mBGColor;
+ abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+ int getBGColor() {
+ return mBGColor;
}
- return shouldSaveView;
}
- @VisibleForTesting
- void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
- @StartingWindowType int suggestType) {
- final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
- null/* TaskSnapshotWindow */, suggestType);
- mStartingWindowRecords.put(taskId, tView);
- }
+ abstract static class SnapshotRecord extends StartingWindowRecord {
+ private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME
+ * visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+ private final Runnable mScheduledRunnable = this::removeImmediately;
+
+ @WindowConfiguration.ActivityType protected final int mActivityType;
+ protected final ShellExecutor mRemoveExecutor;
+
+ SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
+ mActivityType = activityType;
+ mRemoveExecutor = removeExecutor;
+ }
- private void removeWindowNoAnimate(int taskId) {
- mTmpRemovalInfo.taskId = taskId;
- removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
- }
+ @Override
+ public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (immediately) {
+ removeImmediately();
+ } else {
+ scheduleRemove(info.deferRemoveForIme);
+ }
+ }
- void onImeDrawnOnTask(int taskId) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null && record.mTaskSnapshotWindow != null
- && record.mTaskSnapshotWindow.hasImeSurface()) {
- removeWindowNoAnimate(taskId);
+ void scheduleRemove(boolean deferRemoveForIme) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
+ return;
+ }
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+ final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
+ ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+ : DELAY_REMOVAL_TIME_GENERAL;
+ mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Defer removing snapshot surface in %d", delayRemovalTime);
+ }
+
+ protected abstract boolean hasImeSurface();
+
+ @CallSuper
+ protected void removeImmediately() {
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
}
}
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
- final int taskId = removalInfo.taskId;
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- if (record.mDecorView != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing splash screen window for task: %d", taskId);
- if (record.mContentView != null) {
- record.clearSystemBarColor();
- if (immediately
- || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- removeWindowInner(record.mDecorView, false);
- } else {
- if (removalInfo.playRevealAnimation) {
- mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
- removalInfo.windowAnimationLeash, removalInfo.mainFrame,
- () -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime, removalInfo.roundedCornerRadius);
- } else {
- // the SplashScreenView has been copied to client, hide the view to skip
- // default exit animation
- removeWindowInner(record.mDecorView, true);
- }
- }
- } else {
- // shouldn't happen
- Slog.e(TAG, "Found empty splash screen, remove!");
- removeWindowInner(record.mDecorView, false);
- }
+ static class StartingWindowRecordManager {
+ private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+ private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
+ new SparseArray<>();
+ void clearAllWindows() {
+ final int taskSize = mStartingWindowRecords.size();
+ final int[] taskIds = new int[taskSize];
+ for (int i = taskSize - 1; i >= 0; --i) {
+ taskIds[i] = mStartingWindowRecords.keyAt(i);
}
- if (record.mTaskSnapshotWindow != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing task snapshot window for %d", taskId);
- if (immediately) {
- record.mTaskSnapshotWindow.removeImmediately();
- } else {
- record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
- }
+ for (int i = taskSize - 1; i >= 0; --i) {
+ removeWindow(taskIds[i], true);
}
- mStartingWindowRecords.remove(taskId);
}
- }
- private void removeWindowInner(View decorView, boolean hideView) {
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(false, TAG);
- }
- if (hideView) {
- decorView.setVisibility(View.GONE);
+ void addRecord(int taskId, StartingWindowRecord record) {
+ mStartingWindowRecords.put(taskId, record);
}
- mWindowManagerGlobal.removeView(decorView, false /* immediate */);
- }
- /**
- * Record the view or surface for a starting window.
- */
- private static class StartingWindowRecord {
- private final IBinder mAppToken;
- private final View mDecorView;
- private final TaskSnapshotWindow mTaskSnapshotWindow;
- private SplashScreenView mContentView;
- private boolean mSetSplashScreen;
- @StartingWindowType private int mSuggestType;
- private int mBGColor;
- private final long mCreateTime;
- private int mSystemBarAppearance;
- private boolean mDrawsSystemBarBackgrounds;
-
- StartingWindowRecord(IBinder appToken, View decorView,
- TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
- mAppToken = appToken;
- mDecorView = decorView;
- mTaskSnapshotWindow = taskSnapshotWindow;
- if (mTaskSnapshotWindow != null) {
- mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
+ final int taskId = removeInfo.taskId;
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.removeIfPossible(removeInfo, immediately);
+ mStartingWindowRecords.remove(taskId);
}
- mSuggestType = suggestType;
- mCreateTime = SystemClock.uptimeMillis();
}
- private void setSplashScreenView(SplashScreenView splashScreenView) {
- if (mSetSplashScreen) {
- return;
- }
- mContentView = splashScreenView;
- mSetSplashScreen = true;
+ void removeWindow(int taskId, boolean immediately) {
+ mTmpRemovalInfo.taskId = taskId;
+ removeWindow(mTmpRemovalInfo, immediately);
}
- private void parseAppSystemBarColor(Context context) {
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- mDrawsSystemBarBackgrounds = a.getBoolean(
- R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
- if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- }
- if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
- }
- a.recycle();
+ StartingWindowRecord getRecord(int taskId) {
+ return mStartingWindowRecords.get(taskId);
}
- // Reset the system bar color which set by splash screen, make it align to the app.
- private void clearSystemBarColor() {
- if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
- return;
- }
- if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
- final WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
- if (mDrawsSystemBarBackgrounds) {
- lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- } else {
- lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- mDecorView.setLayoutParams(lp);
- }
- mDecorView.getWindowInsetsController().setSystemBarsAppearance(
- mSystemBarAppearance, LIGHT_BARS_MASK);
+ @VisibleForTesting
+ int recordSize() {
+ return mStartingWindowRecords.size();
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index be2e79342d07..bec4ba3bf0d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -21,6 +21,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
@@ -29,7 +30,6 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Color;
-import android.os.IBinder;
import android.os.Trace;
import android.util.SparseIntArray;
import android.window.StartingWindowInfo;
@@ -152,22 +152,23 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
/**
* Called when a task need a starting window.
*/
- public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo windowInfo) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (isSplashScreenType(suggestionType)) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
- suggestionType);
+ if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
+ mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo);
+ } else if (isSplashScreenType(suggestionType)) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = windowInfo.taskSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
- snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
}
- if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
+ if (suggestionType != STARTING_WINDOW_TYPE_NONE
+ && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
int taskId = runningTaskInfo.taskId;
int color = mStartingSurfaceDrawer
.getStartingWindowBackgroundColorForTask(taskId);
@@ -218,11 +219,13 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
removalInfo));
- mSplashScreenExecutor.executeDelayed(() -> {
- synchronized (mTaskBackgroundColors) {
- mTaskBackgroundColors.delete(removalInfo.taskId);
- }
- }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ if (!removalInfo.windowlessSurface) {
+ mSplashScreenExecutor.executeDelayed(() -> {
+ synchronized (mTaskBackgroundColors) {
+ mTaskBackgroundColors.delete(removalInfo.taskId);
+ }
+ }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ }
}
/**
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 a05ed4f24a08..c964df1452e0 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.startingsurface;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -63,24 +62,14 @@ public class TaskSnapshotWindow {
private static final String TAG = StartingWindowController.TAG;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
- private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
- /**
- * The max delay time in milliseconds for removing the task snapshot window with IME visible.
- * Ideally the delay time will be shorter when receiving
- * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
- */
- private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
-
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
private final IWindowSession mSession;
private boolean mHasDrawn;
private final Paint mBackgroundPaint = new Paint();
- private final int mActivityType;
private final int mOrientationOnCreation;
- private final Runnable mScheduledRunnable = this::removeImmediately;
private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
@@ -104,7 +93,6 @@ public class TaskSnapshotWindow {
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
final int orientation = snapshot.getOrientation();
- final int activityType = runningTaskInfo.topActivityType;
final int displayId = runningTaskInfo.displayId;
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -114,16 +102,11 @@ public class TaskSnapshotWindow {
final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
- final TaskDescription taskDescription;
- if (runningTaskInfo.taskDescription != null) {
- taskDescription = runningTaskInfo.taskDescription;
- } else {
- taskDescription = new TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
+ final TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- snapshot, taskDescription, orientation, activityType,
+ snapshot, taskDescription, orientation,
clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
@@ -153,6 +136,8 @@ public class TaskSnapshotWindow {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
+ Slog.w(TAG, "Failed to relayout snapshot starting window");
+ return null;
}
SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
@@ -164,7 +149,7 @@ public class TaskSnapshotWindow {
}
public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
- int currentOrientation, int activityType, Runnable clearWindowHandler,
+ int currentOrientation, Runnable clearWindowHandler,
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
@@ -173,7 +158,6 @@ public class TaskSnapshotWindow {
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mOrientationOnCreation = currentOrientation;
- mActivityType = activityType;
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -186,23 +170,7 @@ public class TaskSnapshotWindow {
return mHasImeSurface;
}
- void scheduleRemove(boolean deferRemoveForIme) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
- final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
- ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
- mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Defer removing snapshot surface in %d", delayRemovalTime);
- }
-
void removeImmediately() {
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
new file mode 100644
index 000000000000..144547885501
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.startingsurface;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SnapshotDrawerUtils;
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSnapshotWindowCreator {
+ private static final int DEFAULT_FADEOUT_DURATION = 233;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+ private final DisplayManager mDisplayManager;
+ private final Context mContext;
+ private final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSnapshotWindowCreator(
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ Context context,
+ DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer,
+ TransactionPool transactionPool) {
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ mContext = context;
+ mDisplayManager = displayManager;
+ mSplashscreenContentDrawer = splashscreenContentDrawer;
+ mTransactionPool = transactionPool;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface,
+ TaskSnapshot snapshot, ShellExecutor removeExecutor) {
+ final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+ final int taskId = runningTaskInfo.taskId;
+ final String title = "Windowless Snapshot " + taskId;
+ final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters(
+ info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(),
+ null /* token */);
+ if (lp == null) {
+ return;
+ }
+ final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ runningTaskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
+ mContext, display, wlw, "WindowlessSnapshotWindowCreator");
+ final Point taskSize = snapshot.getTaskSize();
+ final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y);
+ final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
+ final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ mViewHost.setView(rootLayout, lp);
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot,
+ snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */);
+
+ final ActivityManager.TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
+
+ final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
+ taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
+ runningTaskInfo.topActivityType, removeExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, record);
+ info.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mChildSurface;
+ private final boolean mHasImeSurface;
+
+ SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
+ int bgColor, boolean hasImeSurface, int activityType,
+ ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mViewHost = viewHost;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mHasImeSurface = hasImeSurface;
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ fadeoutThenRelease();
+ }
+
+ void fadeoutThenRelease() {
+ final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f);
+ fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION);
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ fadeOutAnimator.addUpdateListener(animation -> {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ return;
+ }
+ t.setAlpha(mChildSurface, (float) animation.getAnimatedValue());
+ t.apply();
+ });
+
+ fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransactionPool.release(t);
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ });
+ fadeOutAnimator.start();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mHasImeSurface;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
new file mode 100644
index 000000000000..12a0d4054b4d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -0,0 +1,150 @@
+/*
+ * 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.startingsurface;
+
+import static android.graphics.Color.WHITE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
+
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer,
+ Context context,
+ ShellExecutor splashScreenExecutor,
+ DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ TransactionPool pool) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mTransactionPool = pool;
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return;
+ }
+ final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
+ 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+ if (myContext == null) {
+ return;
+ }
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ taskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
+ myContext, display, wlw, "WindowlessSplashWindowCreator");
+ final String title = "Windowless Splash " + taskInfo.taskId;
+ final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters(
+ myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title,
+ PixelFormat.TRANSLUCENT, new Binder());
+ final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ lp.width = windowBounds.width();
+ lp.height = windowBounds.height();
+ final ActivityManager.TaskDescription taskDescription;
+ if (taskInfo.taskDescription != null) {
+ taskDescription = taskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ viewHost.setView(rootLayout, lp);
+
+ final int bgColor = taskDescription.getBackgroundColor();
+ final SplashScreenView splashScreenView = mSplashscreenContentDrawer
+ .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
+ rootLayout.addView(splashScreenView);
+ final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView,
+ wlw.mChildSurface, bgColor);
+ mStartingWindowRecordManager.addRecord(taskInfo.taskId, record);
+ windowInfo.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private SurfaceControlViewHost mViewHost;
+ private final long mCreateTime;
+ private SurfaceControl mChildSurface;
+ private final SplashScreenView mSplashView;
+
+ SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView,
+ SurfaceControl childSurface, int bgColor) {
+ mViewHost = viewHost;
+ mSplashView = splashView;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (!immediately) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ this::release, mCreateTime, 0 /* roundedCornerRadius */);
+ } else {
+ release();
+ }
+ }
+
+ void release() {
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ }
+}
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 bb43d7c1a090..72fc8686f648 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
@@ -22,6 +22,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
@@ -30,6 +31,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
@@ -55,6 +57,7 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
+ final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
@@ -67,10 +70,15 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
+ "isSolidColorSplashScreen=%b, "
+ "legacySplashScreen=%b, "
+ "activityDrawn=%b, "
+ + "windowless=%b, "
+ "topIsHome=%b",
newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
- isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
+ topIsHome);
+ if (windowlessSurface) {
+ return STARTING_WINDOW_TYPE_WINDOWLESS;
+ }
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bdda6a8e926b..bfa63909cd47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -22,6 +22,8 @@ package com.android.wm.shell.sysui;
public class ShellSharedConstants {
// See IPip.aidl
public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See IBubbles.aidl
+ public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
// See ISplitScreen.aidl
public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
// See IOneHanded.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 7a6aec718006..e4d8c32eb5c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java
index 3d0a8fd83819..5fdb60d2d342 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.app.ActivityManager;
import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
index a29e7a085a21..a7e4b0119480 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.UiContext;
import android.content.Context;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index 735d9bce2059..7eed5883043d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.UiContext;
import android.content.Context;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -51,6 +52,9 @@ public class TaskViewFactoryController {
mTaskViewTransitions = null;
}
+ /**
+ * @return the underlying {@link TaskViewFactory}.
+ */
public TaskViewFactory asTaskViewFactory() {
return mImpl;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 080b171f4d40..36c9077a197b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -35,6 +35,7 @@ import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.io.PrintWriter;
@@ -386,8 +387,15 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
return;
}
// Sync Transactions can't operate simultaneously with shell transition collection.
- // The transition animation (upon showing) will sync the location itself.
- if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
+ if (isUsingShellTransitions()) {
+ if (mTaskViewTransitions.hasPending()) {
+ // There is already a transition in-flight. The window bounds will be synced
+ // once it is complete.
+ return;
+ }
+ mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mTaskToken, boundsOnScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 306d6196c553..3b1ce49ebdc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -23,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
import android.view.SurfaceControl;
@@ -40,7 +42,7 @@ import java.util.ArrayList;
* Handles Shell Transitions that involve TaskView tasks.
*/
public class TaskViewTransitions implements Transitions.TransitionHandler {
- private static final String TAG = "TaskViewTransitions";
+ static final String TAG = "TaskViewTransitions";
private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>();
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
@@ -197,6 +199,13 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// visibility is reported in transition.
}
+ void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen);
+ mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ }
+
private void startNextTransition() {
if (mPending.isEmpty()) return;
final PendingTransition pending = mPending.get(0);
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 75112b62c1c6..5a92f7830194 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -25,6 +26,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,6 +43,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
@@ -53,10 +56,12 @@ import java.util.Optional;
* A handler for dealing with transitions involving multiple other handlers. For example: an
* activity in split-screen going into PiP.
*/
-public class DefaultMixedHandler implements Transitions.TransitionHandler {
+public class DefaultMixedHandler implements Transitions.TransitionHandler,
+ RecentsTransitionHandler.RecentsMixedHandler {
private final Transitions mPlayer;
private PipTransitionController mPipHandler;
+ private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private static class MixedTransition {
@@ -68,14 +73,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
/** Pip was entered while handling an intent with its own remoteTransition. */
static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
+ /** Recents transition while split-screen active. */
+ static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
/** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
static final int ANIM_TYPE_GOING_HOME = 1;
+ /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+ static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
final int mType;
- int mAnimType = 0;
+ int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
Transitions.TransitionHandler mLeftoversHandler = null;
@@ -114,7 +125,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional) {
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsHandlerOptional) {
mPlayer = player;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
&& splitScreenControllerOptional.isPresent()) {
@@ -126,6 +138,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mSplitHandler != null) {
mSplitHandler.setMixedHandler(this);
}
+ mRecentsHandler = recentsHandlerOptional.orElse(null);
+ if (mRecentsHandler != null) {
+ mRecentsHandler.addMixer(this);
+ }
}, this);
}
}
@@ -167,19 +183,67 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
+ } else if (mSplitHandler.isSplitActive()
+ && isOpeningType(request.getType())
+ && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+ mPlayer.dispatchRequest(transition, request, this);
+ if (handler == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Lean on the remote transition handler to fetch a proper remote via"
+ + " TransitionFilter");
+ handler = new Pair<>(
+ mPlayer.getRemoteTransitionHandler(),
+ new WindowContainerTransaction());
+ }
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = handler.first;
+ mActiveTransitions.add(mixed);
+ return handler.second;
+ }
+ return null;
+ }
+
+ @Override
+ public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
+ if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+ return this;
}
return null;
}
+ @Override
+ public void setRecentsTransition(IBinder transition) {
+ if (mSplitHandler.isSplitActive()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
+ } else {
+ throw new IllegalStateException("Accepted a recents transition but don't know how to"
+ + " handle it");
+ }
+ }
+
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
+ out.setDebugId(info.getDebugId());
if (withChanges) {
for (int i = 0; i < info.getChanges().size(); ++i) {
out.getChanges().add(info.getChanges().get(i));
}
}
- out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ out.addRoot(info.getRoot(i));
+ }
out.setAnimationOptions(info.getAnimationOptions());
return out;
}
@@ -214,6 +278,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
finishTransaction, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -238,13 +305,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
info.getChanges().remove(i);
}
}
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct, wctCB);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ };
if (pipChange == null) {
if (mixed.mLeftoversHandler != null) {
+ mixed.mInFlightSubAnimations = 1;
if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
- info, startTransaction, finishTransaction, (wct, wctCB) -> {
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct, wctCB);
- })) {
+ info, startTransaction, finishTransaction, finishCB)) {
return true;
}
}
@@ -253,13 +325,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+ " animation because remote-animation likely doesn't support it");
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct, wctCB);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
- };
// Split the transition into 2 parts: the pip part and the rest.
mixed.mInFlightSubAnimations = 2;
// make a new startTransaction because pip's startEnterAnimation "consumes" it so
@@ -439,12 +504,40 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
return true;
}
+ private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations = 0;
+ mActiveTransitions.remove(mixed);
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction, info);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct, wctCB);
+ };
+ mixed.mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+ final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mActiveTransitions.remove(mixed);
+ }
+ return handled;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
- if (mActiveTransitions.get(i) != mergeTarget) continue;
+ if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
@@ -472,6 +565,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
}
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -491,6 +592,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
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 f66c26bb87e4..6e9ecdaf55e7 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
@@ -301,8 +301,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
- // check if no-animation and skip animation if so.
- if (Transitions.isAllNoAnimation(info)) {
+ // Early check if the transition doesn't warrant an animation.
+ if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)) {
startTransaction.apply();
finishTransaction.apply();
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
@@ -327,6 +327,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
+ boolean isDisplayRotationAnimationStarted = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
@@ -350,6 +351,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
+ isDisplayRotationAnimationStarted = true;
continue;
}
} else {
@@ -383,9 +385,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
// No default animation for this, so just update bounds/position.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - info.getRootOffset().x,
- change.getEndAbsBounds().top - info.getRootOffset().y);
+ change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -404,6 +407,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
+ // Hide the invisible surface directly without animating it if there is a display
+ // rotation animation playing.
+ if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(
+ change.getMode())) {
+ startTransaction.hide(change.getLeash());
+ continue;
+ }
+
// The back gesture has animated this change before transition happen, so here we don't
// play the animation again.
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
@@ -474,8 +485,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
- startTransaction, finishTransaction);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+ startTransaction, finishTransaction);
+ }
}
if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +533,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+ mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+ animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
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 485b400f458d..4e3d220f1ea2 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
@@ -63,7 +63,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for %s.", mRemote, transition);
+ + " transition %s for #%d.", mRemote, info.getDebugId());
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -113,9 +113,6 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for %s.", mRemote, transition);
-
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
@@ -154,4 +151,10 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
+ " for %s: %s", transition, remote);
return new WindowContainerTransaction();
}
+
+ @Override
+ public String toString() {
+ return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
+ + mRemote.getRemoteTransition();
+ }
}
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 3c4e8898f215..5b7231c5a5fb 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
@@ -101,8 +101,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
if (pendingRemote == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
- + "explicit remote, search filters for match for %s", transition, info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition doesn't have "
+ + "explicit remote, search filters for match for %s", info);
// If no explicit remote, search filters until one matches
for (int i = mFilters.size() - 1; i >= 0; --i) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
@@ -116,8 +116,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
}
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
- transition, pendingRemote);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+ info.getDebugId(), pendingRemote);
if (pendingRemote == null) return false;
@@ -184,9 +184,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
- transition, remote);
+ final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
+ final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s",
+ remoteTransition);
if (remote == null) return;
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e643170273de..d25318df6b6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -31,7 +31,6 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -162,13 +161,12 @@ class ScreenRotationAnimation {
.setName("RotationLayer")
.build();
- final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
+ TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer);
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
- t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
- mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
+ mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
+ screenshotBuffer.getColorSpace());
}
hardwareBuffer.close();
}
@@ -230,7 +228,7 @@ class ScreenRotationAnimation {
} else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
&& (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
// Display resizes without rotation change.
- final float scale = Math.max((float) mEndWidth / mStartHeight,
+ final float scale = Math.max((float) mEndWidth / mStartWidth,
(float) mEndHeight / mStartHeight);
matrix.setScale(scale, scale);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 0386ec38a3ff..d2795959494a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.transition;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
-import android.util.Log;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -42,9 +41,9 @@ class SleepHandler implements Transitions.TransitionHandler {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mSleepTransitions.remove(transition);
startTransaction.apply();
finishCallback.onTransitionFinished(null, null);
- mSleepTransitions.remove(transition);
return true;
}
@@ -59,7 +58,6 @@ class SleepHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishTransaction) {
- Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
mSleepTransitions.remove(transition);
}
}
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 155990a40836..bdb7d44bad32 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -75,11 +76,29 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Arrays;
-/** Plays transition animations */
+/**
+ * Plays transition animations. Within this player, each transition has a lifecycle.
+ * 1. When a transition is directly started or requested, it is added to "pending" state.
+ * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
+ * 3. When a transition starts animating, it is moved to the "active" state.
+ *
+ * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
+ * --merge--> MERGED --^
+ *
+ * At the moment, only one transition can be animating at a time. While a transition is animating,
+ * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
+ * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
+ * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
+ * "merged" and then the next "ready" transition can attempt to merge.
+ *
+ * Once the "active" transition animation is finished, it will be removed from the "active" list
+ * and then the next "ready" transition can play.
+ */
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
@@ -115,6 +134,19 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
+ /** Transition type to freeform in desktop mode. */
+ public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+
+ /** Transition type to freeform in desktop mode. */
+ public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+
+ /** Transition type to fullscreen from desktop mode. */
+ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
+
+ /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
+ public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -150,14 +182,30 @@ public class Transitions implements RemoteCallable<Transitions> {
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
- boolean mMerged;
boolean mAborted;
TransitionInfo mInfo;
SurfaceControl.Transaction mStartT;
SurfaceControl.Transaction mFinishT;
+
+ /** Ordered list of transitions which have been merged into this one. */
+ private ArrayList<ActiveTransition> mMerged;
+
+ @Override
+ public String toString() {
+ if (mInfo != null && mInfo.getDebugId() >= 0) {
+ return "(#" + mInfo.getDebugId() + ")" + mToken;
+ }
+ return mToken.toString();
+ }
}
- /** Keeps track of currently playing transitions in the order of receipt. */
+ /** Keeps track of transitions which have been started, but aren't ready yet. */
+ private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
+
+ /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+ private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+ /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull Context context,
@@ -291,6 +339,10 @@ public class Transitions implements RemoteCallable<Transitions> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ RemoteTransitionHandler getRemoteTransitionHandler() {
+ return mRemoteTransitionHandler;
+ }
+
/** Registers an observer on the lifecycle of transitions. */
public void registerObserver(@NonNull TransitionObserver observer) {
mObservers.add(observer);
@@ -322,7 +374,8 @@ public class Transitions implements RemoteCallable<Transitions> {
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
- if (mActiveTransitions.isEmpty()) {
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
+ && mReadyTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
@@ -389,8 +442,8 @@ public class Transitions implements RemoteCallable<Transitions> {
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
- if (info.getRootLeash().isValid()) {
- t.show(info.getRootLeash());
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
@@ -408,10 +461,12 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean hasParent = change.getParent() != null;
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (!hasParent) {
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ t.setPosition(leash,
+ change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
@@ -441,9 +496,9 @@ public class Transitions implements RemoteCallable<Transitions> {
}
}
- private int findActiveTransition(IBinder token) {
- for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
- if (mActiveTransitions.get(i).mToken == token) return i;
+ private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken == token) return i;
}
return -1;
}
@@ -476,51 +531,59 @@ public class Transitions implements RemoteCallable<Transitions> {
return hasNoAnimation;
}
+ /**
+ * Check if all changes in this transition are only ordering changes. If so, we won't animate.
+ */
+ static boolean isAllOrderOnly(TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
- final int activeIdx = findActiveTransition(transitionToken);
+ final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
- throw new IllegalStateException("Got transitionReady for non-active transition "
+ throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
- + Arrays.toString(mActiveTransitions.stream().map(
+ + Arrays.toString(mPendingTransitions.stream().map(
+ activeTransition -> activeTransition.mToken).toArray()));
+ }
+ if (activeIdx > 0) {
+ Log.e(TAG, "Transition became ready out-of-order " + mPendingTransitions.get(activeIdx)
+ + ". Expected order: " + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
+ // Move from pending to ready
+ final ActiveTransition active = mPendingTransitions.remove(activeIdx);
+ mReadyTransitions.add(active);
+ active.mInfo = info;
+ active.mStartT = t;
+ active.mFinishT = finishT;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
}
if (info.getType() == TRANSIT_SLEEP) {
- if (activeIdx > 0) {
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
- if (!info.getRootLeash().isValid()) {
- // Shell has some debug settings which makes calling binders with invalid
- // surfaces crash, so replace it with a "real" one.
- info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
- .setContainerLayer().build(), 0, 0);
- }
+ if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
// Sleep starts a process of forcing all prior transitions to finish immediately
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sleep");
finishForSleep(null /* forceFinish */);
return;
}
}
- // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
- // screen-off, so there might no visibility change involved.
- if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
- // Invalid root-leash implies that the transition is empty/no-op, so just do
+ if (info.getRootCount() == 0 && !alwaysReportToKeyguard(info)) {
+ // No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
- transitionToken, info);
- t.apply();
- finishT.apply();
- onAbort(transitionToken);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+ + " abort", active);
+ onAbort(active);
return;
}
@@ -546,43 +609,113 @@ public class Transitions implements RemoteCallable<Transitions> {
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
- t.apply();
- finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
- onAbort(transitionToken);
- releaseSurfaces(info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Non-visible anim so abort: %s", active);
+ onAbort(active);
return;
}
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
- if (activeIdx > 0) {
- // This is now playing at the same time as an existing animation, so try merging it.
- attemptMergeTransition(mActiveTransitions.get(0), active);
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
return;
}
- // The normal case, just play it.
- playTransition(active);
+ processReadyQueue();
}
/**
- * Attempt to merge by delegating the transition start to the handler of the currently
- * playing transition.
+ * Some transitions we always need to report to keyguard even if they are empty.
+ * TODO (b/274954192): Remove this once keyguard dispatching moves to Shell.
*/
- void attemptMergeTransition(@NonNull ActiveTransition playing,
- @NonNull ActiveTransition merging) {
+ private static boolean alwaysReportToKeyguard(TransitionInfo info) {
+ // occlusion status of activities can change while screen is off so there will be no
+ // visibility change but we still need keyguardservice to be notified.
+ if (info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) return true;
+
+ // It's possible for some activities to stop with bad timing (esp. since we can't yet
+ // queue activity transitions initiated by apps) that results in an empty transition for
+ // keyguard going-away. In general, we should should always report Keyguard-going-away.
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) return true;
+
+ return false;
+ }
+
+ void processReadyQueue() {
+ if (mReadyTransitions.isEmpty()) {
+ // Check if idle.
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ + "animations finished");
+ // Run all runnables from the run-when-idle queue.
+ for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+ mRunWhenIdleQueue.get(i).run();
+ }
+ mRunWhenIdleQueue.clear();
+ }
+ return;
+ }
+ final ActiveTransition ready = mReadyTransitions.get(0);
+ if (mActiveTransitions.isEmpty()) {
+ // The normal case, just play it (currently we only support 1 active transition).
+ mReadyTransitions.remove(0);
+ mActiveTransitions.add(ready);
+ if (ready.mAborted) {
+ // finish now since there's nothing to animate. Calls back into processReadyQueue
+ onFinish(ready, null, null);
+ return;
+ }
+ playTransition(ready);
+ // Attempt to merge any more queued-up transitions.
+ processReadyQueue();
+ return;
+ }
+ // An existing animation is playing, so see if we can merge.
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ if (ready.mAborted) {
+ // record as merged since it is no-op. Calls back into processReadyQueue
+ onMerged(playing, ready);
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
- + " another transition %s is still animating. Notify the animating transition"
- + " in case they can be merged", merging.mToken, playing.mToken);
- playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
- playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
+ + " %s is still animating. Notify the animating transition"
+ + " in case they can be merged", ready, playing);
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mToken, (wct, cb) -> onMerged(playing, ready));
+ }
+
+ private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+ merged, playing);
+ int readyIdx = 0;
+ if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+ Log.e(TAG, "Merged transition out-of-order? " + merged);
+ readyIdx = mReadyTransitions.indexOf(merged);
+ if (readyIdx < 0) {
+ Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
+ return;
+ }
+ }
+ mReadyTransitions.remove(readyIdx);
+ if (playing.mMerged == null) {
+ playing.mMerged = new ArrayList<>();
+ }
+ playing.mMerged.add(merged);
+ // if it was aborted, then onConsumed has already been reported.
+ if (merged.mHandler != null && !merged.mAborted) {
+ merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
+ }
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
+ }
+ // See if we should merge another transition.
+ processReadyQueue();
}
private void playTransition(@NonNull ActiveTransition active) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionStarting(active.mToken);
}
@@ -594,7 +727,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
- active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
@@ -602,7 +735,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
- active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
/**
@@ -645,15 +778,28 @@ public class Transitions implements RemoteCallable<Transitions> {
return null;
}
- /** Special version of finish just for dealing with no-op/invalid transitions. */
- private void onAbort(IBinder transition) {
- onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
- }
+ /** Aborts a transition. This will still queue it up to maintain order. */
+ private void onAbort(ActiveTransition transition) {
+ // apply immediately since they may be "parallel" operations: We currently we use abort for
+ // thing which are independent to other transitions (like starting-window transfer).
+ transition.mStartT.apply();
+ transition.mFinishT.apply();
+ transition.mAborted = true;
- private void onFinish(IBinder transition,
- @Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB) {
- onFinish(transition, wct, wctCB, false /* abort */);
+ if (transition.mHandler != null) {
+ // Notifies to clean-up the aborted transition.
+ transition.mHandler.onTransitionConsumed(
+ transition.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+
+ releaseSurfaces(transition.mInfo);
+
+ // This still went into the queue (to maintain the correct finish ordering).
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
+ return;
+ }
+ processReadyQueue();
}
/**
@@ -665,167 +811,97 @@ public class Transitions implements RemoteCallable<Transitions> {
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder transition,
+ private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB,
- boolean abort) {
- int activeIdx = findActiveTransition(transition);
+ @Nullable WindowContainerTransactionCallback wctCB) {
+ int activeIdx = mActiveTransitions.indexOf(active);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
- + " a handler didn't properly deal with a merge.", new RuntimeException());
- return;
- } else if (activeIdx > 0) {
- // This transition was merged.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
- + " %s", abort, transition);
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mMerged = true;
- active.mAborted = abort;
- if (active.mHandler != null) {
- active.mHandler.onTransitionConsumed(
- active.mToken, abort, abort ? null : active.mFinishT);
- }
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionMerged(
- active.mToken, mActiveTransitions.get(0).mToken);
- }
+ + " a handler didn't properly deal with a merge. " + active,
+ new RuntimeException());
return;
+ } else if (activeIdx != 0) {
+ // Relevant right now since we only allow 1 active transition at a time.
+ Log.e(TAG, "Finishing a transition out of order. " + active);
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mAborted = abort;
- if (active.mAborted && active.mHandler != null) {
- // Notifies to clean-up the aborted transition.
- active.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
- }
+ mActiveTransitions.remove(activeIdx);
+
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ + "(aborted=%b), notifying core %s", active.mAborted, active);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
active.mStartT.clear();
}
- // Merge all relevant transactions together
+ // Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
- for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
- final ActiveTransition toMerge = mActiveTransitions.get(iA);
- if (!toMerge.mMerged) break;
- // Include start. It will be a no-op if it was already applied. Otherwise, we need it
- // to maintain consistent state.
- if (toMerge.mStartT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mStartT;
- } else {
- fullFinish.merge(toMerge.mStartT);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ final ActiveTransition toMerge = active.mMerged.get(iM);
+ // Include start. It will be a no-op if it was already applied. Otherwise, we need
+ // it to maintain consistent state.
+ if (toMerge.mStartT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mStartT;
+ } else {
+ fullFinish.merge(toMerge.mStartT);
+ }
}
- }
- if (toMerge.mFinishT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mFinishT;
- } else {
- fullFinish.merge(toMerge.mFinishT);
+ if (toMerge.mFinishT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mFinishT;
+ } else {
+ fullFinish.merge(toMerge.mFinishT);
+ }
}
}
}
if (fullFinish != null) {
fullFinish.apply();
}
- // Now perform all the finishes.
+ // Now perform all the finish callbacks (starting with the playing one and then all the
+ // transitions merged into it).
releaseSurfaces(active.mInfo);
- mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(transition, wct, wctCB);
- while (activeIdx < mActiveTransitions.size()) {
- if (!mActiveTransitions.get(activeIdx).mMerged) break;
- ActiveTransition merged = mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
- releaseSurfaces(merged.mInfo);
- }
- // sift through aborted transitions
- while (mActiveTransitions.size() > activeIdx
- && mActiveTransitions.get(activeIdx).mAborted) {
- ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
- // Notifies to clean-up the aborted transition.
- if (aborted.mHandler != null) {
- aborted.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
- }
- mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(aborted.mToken, true);
+ mOrganizer.finishTransition(active.mToken, wct, wctCB);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ ActiveTransition merged = active.mMerged.get(iM);
+ mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
- releaseSurfaces(aborted.mInfo);
+ active.mMerged.clear();
}
- if (mActiveTransitions.size() <= activeIdx) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
- + "finished");
- // Run all runnables from the run-when-idle queue.
- for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
- mRunWhenIdleQueue.get(i).run();
- }
- mRunWhenIdleQueue.clear();
- return;
- }
- // Start animating the next active transition
- final ActiveTransition next = mActiveTransitions.get(activeIdx);
- if (next.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
- + " finished, but it isn't ready yet.");
- return;
+
+ // Now that this is done, check the ready queue for more work.
+ processReadyQueue();
+ }
+
+ private boolean isTransitionKnown(IBinder token) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ if (mPendingTransitions.get(i).mToken == token) return true;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
- + " finished, so start the next one.");
- playTransition(next);
- // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
- // finished immediately)
- activeIdx = findActiveTransition(next.mToken);
- if (activeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in the
- // re-entered call.
- return;
+ for (int i = 0; i < mReadyTransitions.size(); ++i) {
+ if (mReadyTransitions.get(i).mToken == token) return true;
}
-
- // This logic is also convoluted because 'next' may finish immediately in response to any of
- // the merge requests (eg. if it decided to "cancel" itself).
- int mergeIdx = activeIdx + 1;
- while (mergeIdx < mActiveTransitions.size()) {
- ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
- if (mergeCandidate.mAborted) {
- // transition was aborted, so we can skip for now (still leave it in the list
- // so that it gets cleaned-up in the right order).
- ++mergeIdx;
- continue;
- }
- if (mergeCandidate.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
- + " %s is not ready yet", mergeCandidate.mToken);
- // The later transition should not be merged if the prior one is not ready.
- return;
- }
- if (mergeCandidate.mMerged) {
- throw new IllegalStateException("Can't merge a transition after not-merging"
- + " a preceding one.");
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ final ActiveTransition active = mActiveTransitions.get(i);
+ if (active.mToken == token) return true;
+ if (active.mMerged == null) continue;
+ for (int m = 0; m < active.mMerged.size(); ++m) {
+ if (active.mMerged.get(m).mToken == token) return true;
}
- attemptMergeTransition(next, mergeCandidate);
- mergeIdx = findActiveTransition(mergeCandidate.mToken);
- if (mergeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in
- // the re-entered call.
- return;
- }
- ++mergeIdx;
}
+ return false;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
- if (findActiveTransition(transitionToken) >= 0) {
+ if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
@@ -858,24 +934,24 @@ public class Transitions implements RemoteCallable<Transitions> {
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
- int insertIdx = 0;
- for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
- if (mActiveTransitions.get(insertIdx).mInfo == null) {
- // A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
- // made this request, so insert this request beforehand to keep order in sync.
- break;
- }
- }
- mActiveTransitions.add(insertIdx, active);
+ // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
+ // is already collecting that transition on core-side, so it will be the next one to
+ // become ready. There may already be pending transitions added as part of direct
+ // `startNewTransition` but if we have a request now, it means WM created the request
+ // transition before it acknowledged any of the pending `startNew` transitions. So, insert
+ // it at the front.
+ mPendingTransitions.add(0, active);
}
/** Start a new transition directly. */
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);
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
- mActiveTransitions.add(active);
+ mPendingTransitions.add(active);
return active.mToken;
}
@@ -894,27 +970,40 @@ public class Transitions implements RemoteCallable<Transitions> {
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
- private void finishForSleep(@Nullable IBinder forceFinish) {
- if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
+ private void finishForSleep(@Nullable ActiveTransition forceFinish) {
+ if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
+ || mSleepHandler.mSleepTransitions.isEmpty()) {
+ // Done finishing things.
+ // Prevent any weird leaks... shouldn't happen though.
+ mSleepHandler.mSleepTransitions.clear();
return;
}
- if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
- Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
- + mActiveTransitions.get(0).mToken);
- onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+ if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
+ Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + forceFinish);
+ forceFinish.mAborted = true;
+ // Last notify of it being consumed. Note: mHandler should never be null,
+ // but check just to be safe.
+ if (forceFinish.mHandler != null) {
+ forceFinish.mHandler.onTransitionConsumed(
+ forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+ onFinish(forceFinish, null, null);
}
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
+ final TransitionInfo dummyInfo = new TransitionInfo(TRANSIT_SLEEP, 0 /* flags */);
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
final ActiveTransition playing = mActiveTransitions.get(0);
- int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
+ int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
if (sleepIdx >= 0) {
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
- final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
- playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
+ final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge SLEEP %s"
+ + " into %s", nextSleep, playing);
+ playing.mHandler.mergeAnimation(nextSleep.mToken, dummyInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
- Log.e(TAG, "Couldn't find sleep transition in active list: "
+ Log.e(TAG, "Couldn't find sleep transition in ready list: "
+ mSleepHandler.mSleepTransitions.get(0));
}
// it's possible to complete immediately. If that happens, just repeat the signal
@@ -922,8 +1011,7 @@ public class Transitions implements RemoteCallable<Transitions> {
// finishing immediately.
if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
// Give it a (very) short amount of time to process it before forcing.
- mMainExecutor.executeDelayed(
- () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
+ mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 86ca292399cb..fe0a3fb7b9dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
}
private float[] getBackgroundColor(Context context) {
- int colorInt = context.getResources().getColor(R.color.taskbar_background);
+ int colorInt = context.getResources().getColor(R.color.unfold_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 8c6e1e7f5f1b..ce102917352d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -31,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -90,6 +91,15 @@ public class TransitionUtil {
&& !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
}
+ /** Returns `true` if `change` is only re-ordering. */
+ public static boolean isOrderOnly(TransitionInfo.Change change) {
+ return change.getMode() == TRANSIT_CHANGE
+ && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
+ && change.getStartAbsBounds().equals(change.getEndAbsBounds())
+ && (change.getLastParent() == null
+ || change.getLastParent().equals(change.getParent()));
+ }
+
/**
* Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
* MUST call `test` in the same order that the changes appear in the TransitionInfo.
@@ -139,11 +149,12 @@ public class TransitionUtil {
// changes should be ordered top-to-bottom in z
final int mode = change.getMode();
- t.reparent(leash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
final Rect absBounds =
(mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
- t.setPosition(leash, absBounds.left - info.getRootOffset().x,
- absBounds.top - info.getRootOffset().y);
+ t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+ absBounds.top - info.getRoot(rootIdx).getOffset().y);
// Put all the OPEN/SHOW on top
if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +190,13 @@ public class TransitionUtil {
// making leashes means we have to handle them specially.
return change.getLeash();
}
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName(change.getLeash().toString() + "_transition-leash")
.setContainerLayer()
// Initial the surface visible to respect the visibility of the original surface.
.setHidden(false)
- .setParent(info.getRootLeash())
+ .setParent(info.getRoot(rootIdx).getLeash())
.build();
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +273,18 @@ public class TransitionUtil {
target.setRotationChange(change.getEndRotation() - change.getStartRotation());
return target;
}
+
+ /**
+ * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+ * the start display. If there is no display, it will fallback on the 0th root in the
+ * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+ */
+ public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo info) {
+ int rootIdx = info.findRootIndex(change.getEndDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ rootIdx = info.findRootIndex(change.getStartDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ return 0;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9224b3cbd798..e8a6a159cb19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,11 +23,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Handler;
+import android.os.IBinder;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -72,6 +74,16 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
+ public void onTransitionReady(IBinder transition, TransitionInfo info,
+ TransitionInfo.Change change) {}
+
+ @Override
+ public void onTransitionMerged(IBinder merged, IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(IBinder transition) {}
+
+ @Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
@@ -115,7 +127,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
}
}
@@ -127,7 +139,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
}
@Override
@@ -180,7 +192,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
- windowDecoration.relayout(taskInfo, startT, finishT);
+ windowDecoration.relayout(taskInfo, startT, finishT,
+ false /* applyStartTransactionOnDraw */);
setupCaptionColor(taskInfo, windowDecoration);
}
@@ -193,6 +206,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final DragDetector mDragDetector;
private int mDragPointerId = -1;
+ private boolean mIsDragging;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
@@ -223,19 +237,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (v.getId() != R.id.caption) {
return false;
}
- mDragDetector.onMotionEvent(e);
-
- if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return false;
+ if (e.getAction() == MotionEvent.ACTION_DOWN) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (!taskInfo.isFocused) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ }
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, true /* onTop */);
- mSyncQueue.queue(wct);
- return true;
+ return mDragDetector.onMotionEvent(e);
}
/**
@@ -253,20 +263,24 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e05b46..116af7094e13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -90,15 +90,15 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- relayout(taskInfo, t, t);
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
+ // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
+ // synced with the buffer transaction (that draws the View). Both will be shown on screen
+ // at the same, whereas applying them independently causes flickering. See b/270202228.
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
}
void relayout(RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean applyStartTransactionOnDraw) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -110,19 +110,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
+ mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
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 2aa6d1217fa6..49a5eac5fd07 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,15 +22,25 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
+import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -39,9 +49,13 @@ import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,8 +68,10 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
+import java.util.function.Supplier;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -64,6 +80,7 @@ import java.util.Optional;
public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "DesktopModeWindowDecorViewModel";
+
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -83,9 +100,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
+ private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
private Optional<SplitScreenController> mSplitScreenController;
+ private ValueAnimator mDragToDesktopValueAnimator;
+ private final Rect mDragToDesktopAnimationStartBounds = new Rect();
+ private boolean mDragToDesktopAnimationStarted;
+ private float mCaptionDragStartX;
+
+ // These values keep track of any transitions to freeform to stop relayout from running on
+ // changing task so that shellTransitions has a chance to animate the transition
+ private int mPauseRelayoutForTask = -1;
+ private IBinder mTransitionPausingRelayout;
+
public DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
@@ -107,7 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
desktopTasksController,
splitScreenController,
new DesktopModeWindowDecoration.Factory(),
- new InputMonitorFactory());
+ new InputMonitorFactory(),
+ SurfaceControl.Transaction::new);
}
@VisibleForTesting
@@ -122,7 +151,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
- InputMonitorFactory inputMonitorFactory) {
+ InputMonitorFactory inputMonitorFactory,
+ Supplier<SurfaceControl.Transaction> transactionFactory) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -136,6 +166,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
+ mTransactionFactory = transactionFactory;
}
@Override
@@ -155,6 +186,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
+ public void onTransitionReady(
+ @NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change) {
+ if (change.getMode() == WindowManager.TRANSIT_CHANGE
+ && info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE) {
+ mTransitionPausingRelayout = transition;
+ }
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+ if (merged.equals(mTransitionPausingRelayout)) {
+ mTransitionPausingRelayout = playing;
+ }
+ }
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition) {
+ if (transition.equals(mTransitionPausingRelayout)) {
+ mPauseRelayoutForTask = -1;
+ }
+ }
+
+ @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
@@ -165,8 +221,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
incrementEventReceiverTasks(taskInfo.displayId);
}
- decoration.relayout(taskInfo);
- setupCaptionColor(taskInfo, decoration);
+ // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
+ // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
+ // and interferes with the transition animation that is playing at the same time.
+ if (taskInfo.taskId != mPauseRelayoutForTask) {
+ decoration.relayout(taskInfo);
+ }
}
@Override
@@ -187,7 +247,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
}
}
@@ -199,7 +259,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
}
@Override
@@ -223,6 +283,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private boolean mIsDragging;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
@@ -251,8 +312,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
- } else if (id == R.id.caption_handle) {
- decoration.createHandleMenu();
+ } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
+ if (!decoration.isHandleMenuActive()) {
+ moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+ decoration.createHandleMenu();
+ } else {
+ decoration.closeHandleMenu();
+ }
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
@@ -261,7 +327,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
- decoration.setButtonVisibility(false);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
}
@@ -270,25 +335,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
- if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
+ if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
+ && id != R.id.open_menu_button && id != R.id.close_window) {
return false;
}
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDragDetector.onMotionEvent(e);
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return mDragDetector.isDragEvent();
- }
- return false;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- boolean res = mDragDetector.isDragEvent();
- mDragDetector.onMotionEvent(e);
- return res;
- default:
- mDragDetector.onMotionEvent(e);
- return mDragDetector.isDragEvent();
+ moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+ return mDragDetector.onMotionEvent(e);
+ }
+
+ private void moveTaskToFront(RunningTaskInfo taskInfo) {
+ if (!taskInfo.isFocused) {
+ mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+ mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
}
}
@@ -312,31 +370,32 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
- 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ 0 /* ctrlType */, e.getRawX(0),
+ e.getRawY(0));
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
+ final DesktopModeWindowDecoration decoration =
+ mWindowDecorByTaskId.get(mTaskId);
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- final int statusBarHeight = mDisplayController
- .getDisplayLayout(taskInfo.displayId).stableInsets().top;
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Switch a single task to fullscreen
- mDesktopTasksController.ifPresent(
- c -> c.moveToFullscreen(taskInfo));
- }
- }
- break;
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
+ e.getRawY(dragPointerIdx)));
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
@@ -420,7 +479,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
if (DesktopModeStatus.isProto2Enabled()) {
if (relevantDecor == null
- || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
+ || mTransitionDragActive) {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
@@ -461,8 +521,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
DesktopModeWindowDecoration relevantDecor) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
+ mCaptionDragStartX = ev.getX();
// Begin drag through status bar if applicable.
if (relevantDecor != null) {
+ mDragToDesktopAnimationStartBounds.set(
+ relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
// In proto2 any full screen task can be dragged to freeform
@@ -478,33 +541,142 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
case MotionEvent.ACTION_UP: {
if (relevantDecor == null) {
+ mDragToDesktopAnimationStarted = false;
mTransitionDragActive = false;
return;
}
if (mTransitionDragActive) {
mTransitionDragActive = false;
- final int statusBarHeight = mDisplayController
- .getDisplayLayout(relevantDecor.mTaskInfo.displayId).stableInsets().top;
- if (ev.getY() > statusBarHeight) {
+ final int statusBarHeight = getStatusBarHeight(
+ relevantDecor.mTaskInfo.displayId);
+ if (ev.getY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
- mDesktopTasksController.ifPresent(
- c -> c.moveToDesktop(relevantDecor.mTaskInfo));
+ mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
+ centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
-
+ mDragToDesktopAnimationStarted = false;
+ return;
+ } else if (mDragToDesktopAnimationStarted) {
+ Point startPosition = new Point((int) ev.getX(), (int) ev.getY());
+ mDesktopTasksController.ifPresent(
+ c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ startPosition));
+ mDragToDesktopAnimationStarted = false;
return;
}
}
relevantDecor.checkClickEvent(ev);
break;
}
+
+ case MotionEvent.ACTION_MOVE: {
+ if (relevantDecor == null) {
+ return;
+ }
+ if (mTransitionDragActive) {
+ mDesktopTasksController.ifPresent(
+ c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getY()));
+ final int statusBarHeight = getStatusBarHeight(
+ relevantDecor.mTaskInfo.displayId);
+ if (ev.getY() > statusBarHeight) {
+ if (!mDragToDesktopAnimationStarted) {
+ mDragToDesktopAnimationStarted = true;
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToFreeform(relevantDecor.mTaskInfo,
+ mDragToDesktopAnimationStartBounds));
+ startAnimation(relevantDecor);
+ }
+ }
+ if (mDragToDesktopAnimationStarted) {
+ Transaction t = mTransactionFactory.get();
+ float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
+ * mDragToDesktopAnimationStartBounds.width();
+ float x = ev.getX() - (width / 2);
+ t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
+ t.apply();
+ }
+ }
+ break;
+ }
+
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
+ mDragToDesktopAnimationStarted = false;
}
}
}
+ /**
+ * Gets bounds of a scaled window centered relative to the screen bounds
+ * @param scale the amount to scale to relative to the Screen Bounds
+ */
+ private Rect calculateFreeformBounds(float scale) {
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+
+ final float adjustmentPercentage = (1f - scale) / 2;
+ final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage),
+ (int) (screenHeight * adjustmentPercentage),
+ (int) (screenWidth * (adjustmentPercentage + scale)),
+ (int) (screenHeight * (adjustmentPercentage + scale)));
+ return endBounds;
+ }
+
+ /**
+ * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
+ * @param relevantDecor the window decor of the task to be animated
+ * @param ev the motion event that triggers the animation
+ */
+ private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
+ MotionEvent ev) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl sc = relevantDecor.mTaskSurface;
+ final Rect endBounds = calculateFreeformBounds(DRAG_FREEFORM_SCALE);
+ final Transaction t = mTransactionFactory.get();
+ final float diffX = endBounds.centerX() - ev.getX();
+ final float diffY = endBounds.top - ev.getY();
+ final float startingX = ev.getX() - DRAG_FREEFORM_SCALE
+ * mDragToDesktopAnimationStartBounds.width() / 2;
+
+ animator.addUpdateListener(animation -> {
+ final float animatorValue = (float) animation.getAnimatedValue();
+ final float x = startingX + diffX * animatorValue;
+ final float y = ev.getY() + diffY * animatorValue;
+ t.setPosition(sc, x, y);
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDesktopTasksController.ifPresent(
+ c -> c.onDragPositioningEndThroughStatusBar(
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
+ }
+ });
+ animator.start();
+ }
+
+ private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
+ mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
+ mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final Transaction t = mTransactionFactory.get();
+ mDragToDesktopValueAnimator.addUpdateListener(animation -> {
+ final float animatorValue = (float) animation.getAnimatedValue();
+ SurfaceControl sc = focusedDecor.mTaskSurface;
+ t.setScale(sc, animatorValue, animatorValue);
+ t.apply();
+ });
+
+ mDragToDesktopValueAnimator.start();
+ }
+
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController.isPresent()
@@ -551,6 +723,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return focusedDecor;
}
+ private int getStatusBarHeight(int displayId) {
+ return mDisplayController.getDisplayLayout(displayId).stableInsets().top;
+ }
+
private void createInputChannel(int displayId) {
final InputManager inputManager = mContext.getSystemService(InputManager.class);
final InputMonitor inputMonitor =
@@ -567,13 +743,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private void setupCaptionColor(RunningTaskInfo taskInfo,
- DesktopModeWindowDecoration decoration) {
- if (taskInfo == null || taskInfo.taskDescription == null) return;
- final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
- }
-
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.isProto2Enabled()
@@ -612,8 +781,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
- windowDecoration.relayout(taskInfo, startT, finishT);
- setupCaptionColor(taskInfo, windowDecoration);
+ windowDecoration.relayout(taskInfo, startT, finishT,
+ false /* applyStartTransactionOnDraw */);
incrementEventReceiverTasks(taskInfo.displayId);
}
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 72da1089c91c..af3fb0ead55c 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
@@ -17,19 +17,20 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.util.Log;
import android.view.Choreographer;
@@ -37,29 +38,38 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.Button;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
import android.window.WindowContainerTransaction;
+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.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link DesktopModeWindowDecorViewModel}. The caption bar contains a handle, back button, and
- * close button.
+ * {@link DesktopModeWindowDecorViewModel}.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
+ private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private DragPositioningCallback mDragPositioningCallback;
@@ -67,16 +77,30 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
- private final int mCaptionMenuHeightId = R.dimen.freeform_decor_caption_menu_height;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
- private boolean mDesktopActive;
- private AdditionalWindow mHandleMenu;
- private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
- private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
- private final int mHandleMenuCornerRadiusId = R.dimen.caption_menu_corner_radius;
- private PointF mHandleMenuPosition = new PointF();
+ private final PointF mHandleMenuAppInfoPillPosition = new PointF();
+ private final PointF mHandleMenuWindowingPillPosition = new PointF();
+ private final PointF mHandleMenuMoreActionsPillPosition = new PointF();
+
+ // Collection of additional windows that comprise the handle menu.
+ private AdditionalWindow mHandleMenuAppInfoPill;
+ private AdditionalWindow mHandleMenuWindowingPill;
+ private AdditionalWindow mHandleMenuMoreActionsPill;
+
+ private Drawable mAppIcon;
+ private CharSequence mAppName;
+
+ private int mMenuWidth;
+ private int mMarginMenuTop;
+ private int mMarginMenuStart;
+ private int mMarginMenuSpacing;
+ private int mAppInfoPillHeight;
+ private int mWindowingPillHeight;
+ private int mMoreActionsPillHeight;
+ private int mShadowRadius;
+ private int mCornerRadius;
DesktopModeWindowDecoration(
Context context,
@@ -92,7 +116,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
- mDesktopActive = DesktopModeStatus.isActive(mContext);
+
+ loadAppInfo();
+ loadHandleMenuDimensions();
+ }
+
+ private void loadHandleMenuDimensions() {
+ final Resources resources = mDecorWindowContext.getResources();
+ mMenuWidth = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_width);
+ mMarginMenuTop = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_top);
+ mMarginMenuStart = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_start);
+ mMarginMenuSpacing = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
+ mAppInfoPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_app_info_pill_height);
+ mWindowingPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+ mShadowRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_shadow_radius);
+ mCornerRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_corner_radius);
+ mMoreActionsPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+ }
+
+ @Override
+ protected Configuration getConfigurationWithOverrides(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ Configuration configuration = taskInfo.getConfiguration();
+ if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+ // Density is overridden for desktop tasks. Keep system density for window decoration.
+ configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
+ }
+ return configuration;
}
void setCaptionListeners(
@@ -114,15 +173,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- relayout(taskInfo, t, t);
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
+ // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
+ // synced with the buffer transaction (that draws the View). Both will be shown on screen
+ // at the same, whereas applying them independently causes flickering. See b/270202228.
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean applyStartTransactionOnDraw) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -130,23 +189,34 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
+ if (mHandleMenuAppInfoPill != null) {
+ updateHandleMenuPillPositions();
+ startT.setPosition(mHandleMenuAppInfoPill.mWindowSurface,
+ mHandleMenuAppInfoPillPosition.x, mHandleMenuAppInfoPillPosition.y);
+
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+ if (shouldShowWindowingPill) {
+ startT.setPosition(mHandleMenuWindowingPill.mWindowSurface,
+ mHandleMenuWindowingPillPosition.x, mHandleMenuWindowingPillPosition.y);
+ }
+
+ startT.setPosition(mHandleMenuMoreActionsPill.mWindowSurface,
+ mHandleMenuMoreActionsPillPosition.x, mHandleMenuMoreActionsPillPosition.y);
+ }
+
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
+ final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
+ taskInfo.getWindowingMode());
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
+ mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
+ mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -159,23 +229,29 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return;
}
if (oldRootView != mResult.mRootView) {
- setupRootView();
+ if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) {
+ mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder(
+ mResult.mRootView,
+ mOnCaptionTouchListener,
+ mOnCaptionButtonClickListener
+ );
+ } else if (mRelayoutParams.mLayoutResId
+ == R.layout.desktop_mode_app_controls_window_decor) {
+ mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
+ mResult.mRootView,
+ mOnCaptionTouchListener,
+ mOnCaptionButtonClickListener,
+ mAppName,
+ mAppIcon
+ );
+ } else {
+ throw new IllegalArgumentException("Unexpected layout resource id");
+ }
}
+ mWindowDecorViewHolder.bindData(mTaskInfo);
- // If this task is not focused, do not show caption.
- setCaptionVisibility(mTaskInfo.isFocused);
-
- if (mTaskInfo.isFocused) {
- if (DesktopModeStatus.isProto2Enabled()) {
- updateButtonVisibility();
- } else if (DesktopModeStatus.isProto1Enabled()) {
- // Only handle should show if Desktop Mode is inactive.
- boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus) {
- mDesktopActive = desktopCurrentStatus;
- setButtonVisibility(mDesktopActive);
- }
- }
+ if (!mTaskInfo.isFocused) {
+ closeHandleMenu();
}
if (!isDragResizeable) {
@@ -206,170 +282,167 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
}
- /**
- * Sets up listeners when a new root view is created.
- */
- private void setupRootView() {
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- caption.setOnTouchListener(mOnCaptionTouchListener);
- final View handle = caption.findViewById(R.id.caption_handle);
- handle.setOnTouchListener(mOnCaptionTouchListener);
- handle.setOnClickListener(mOnCaptionButtonClickListener);
- if (DesktopModeStatus.isProto1Enabled()) {
- final View back = caption.findViewById(R.id.back_button);
- back.setOnClickListener(mOnCaptionButtonClickListener);
- final View close = caption.findViewById(R.id.close_window);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- }
- updateButtonVisibility();
+ boolean isHandleMenuActive() {
+ return mHandleMenuAppInfoPill != null;
}
- private void setupHandleMenu() {
- final View menu = mHandleMenu.mWindowViewHost.getView();
- final View fullscreen = menu.findViewById(R.id.fullscreen_button);
- fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
- final View desktop = menu.findViewById(R.id.desktop_button);
- if (DesktopModeStatus.isProto2Enabled()) {
- desktop.setOnClickListener(mOnCaptionButtonClickListener);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- desktop.setVisibility(View.GONE);
- }
- final View split = menu.findViewById(R.id.split_screen_button);
- split.setOnClickListener(mOnCaptionButtonClickListener);
- final View close = menu.findViewById(R.id.close_button);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- final View collapse = menu.findViewById(R.id.collapse_menu_button);
- collapse.setOnClickListener(mOnCaptionButtonClickListener);
- menu.setOnTouchListener(mOnCaptionTouchListener);
-
- String packageName = mTaskInfo.baseActivity.getPackageName();
+ private void loadAppInfo() {
+ String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
- // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
try {
+ IconProvider provider = new IconProvider(mContext);
+ mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ PackageManager.ComponentInfoFlags.of(0)));
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
- final ImageView appIcon = menu.findViewById(R.id.application_icon);
- appIcon.setImageDrawable(pm.getApplicationIcon(applicationInfo));
- final TextView appName = menu.findViewById(R.id.application_name);
- appName.setText(pm.getApplicationLabel(applicationInfo));
+ mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package not found: " + packageName, e);
}
}
- /**
- * Sets caption visibility based on task focus.
- * Note: Only applicable to Desktop Proto 1; Proto 2 only closes handle menu on focus loss
- * @param visible whether or not the caption should be visible
- */
- private void setCaptionVisibility(boolean visible) {
- if (!visible) closeHandleMenu();
- if (!DesktopModeStatus.isProto1Enabled()) return;
- final int v = visible ? View.VISIBLE : View.GONE;
- final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- captionView.setVisibility(v);
-
- }
-
- /**
- * Sets the visibility of buttons and color of caption based on desktop mode status
- */
- void updateButtonVisibility() {
- if (DesktopModeStatus.isProto2Enabled()) {
- setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- mDesktopActive = DesktopModeStatus.isActive(mContext);
- setButtonVisibility(mDesktopActive);
+ private void closeDragResizeListener() {
+ if (mDragResizeListener == null) {
+ return;
}
+ mDragResizeListener.close();
+ mDragResizeListener = null;
}
/**
- * Show or hide buttons
+ * Create and display handle menu window
*/
- void setButtonVisibility(boolean visible) {
- final int visibility = visible && DesktopModeStatus.isProto1Enabled()
- ? View.VISIBLE : View.GONE;
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final View back = caption.findViewById(R.id.back_button);
- final View close = caption.findViewById(R.id.close_window);
- back.setVisibility(visibility);
- close.setVisibility(visibility);
- final int buttonTintColorRes =
- mDesktopActive ? R.color.decor_button_dark_color
- : R.color.decor_button_light_color;
- final ColorStateList buttonTintColor =
- caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- final View handle = caption.findViewById(R.id.caption_handle);
- final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
- handleBackground.setTintList(buttonTintColor);
- }
+ void createHandleMenu() {
+ final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ updateHandleMenuPillPositions();
- boolean isHandleMenuActive() {
- return mHandleMenu != null;
- }
+ createAppInfoPill(t, ssg);
- void setCaptionColor(int captionColor) {
- if (mResult.mRootView == null) {
- return;
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+ if (shouldShowWindowingPill) {
+ createWindowingPill(t, ssg);
}
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
- captionDrawable.setColor(captionColor);
+ createMoreActionsPill(t, ssg);
- final int buttonTintColorRes =
- Color.valueOf(captionColor).luminance() < 0.5
- ? R.color.decor_button_light_color
- : R.color.decor_button_dark_color;
- final ColorStateList buttonTintColor =
- caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+ ssg.addTransaction(t);
+ ssg.markSyncReady();
+ setupHandleMenu(shouldShowWindowingPill);
+ }
- final View handle = caption.findViewById(R.id.caption_handle);
- final Drawable handleBackground = handle.getBackground();
- handleBackground.setTintList(buttonTintColor);
- if (DesktopModeStatus.isProto1Enabled()) {
- final View back = caption.findViewById(R.id.back_button);
- final Drawable backBackground = back.getBackground();
- backBackground.setTintList(buttonTintColor);
- final View close = caption.findViewById(R.id.close_window);
- final Drawable closeBackground = close.getBackground();
- closeBackground.setTintList(buttonTintColor);
- }
+ private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ final int x = (int) mHandleMenuAppInfoPillPosition.x;
+ final int y = (int) mHandleMenuAppInfoPillPosition.y;
+ mHandleMenuAppInfoPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
+ "Menu's app info pill",
+ t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
}
- private void closeDragResizeListener() {
- if (mDragResizeListener == null) {
- return;
+ private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ final int x = (int) mHandleMenuWindowingPillPosition.x;
+ final int y = (int) mHandleMenuWindowingPillPosition.y;
+ mHandleMenuWindowingPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
+ "Menu's windowing pill",
+ t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+ }
+
+ private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ final int x = (int) mHandleMenuMoreActionsPillPosition.x;
+ final int y = (int) mHandleMenuMoreActionsPillPosition.y;
+ mHandleMenuMoreActionsPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
+ "Menu's more actions pill",
+ t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+ }
+
+ private void setupHandleMenu(boolean windowingPillShown) {
+ // App Info pill setup.
+ final View appInfoPillView = mHandleMenuAppInfoPill.mWindowViewHost.getView();
+ final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
+ final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
+ final TextView appName = appInfoPillView.findViewById(R.id.application_name);
+ collapseBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ appInfoPillView.setOnTouchListener(mOnCaptionTouchListener);
+ appIcon.setImageDrawable(mAppIcon);
+ appName.setText(mAppName);
+
+ // Windowing pill setup.
+ if (windowingPillShown) {
+ final View windowingPillView = mHandleMenuWindowingPill.mWindowViewHost.getView();
+ final ImageButton fullscreenBtn = windowingPillView.findViewById(
+ R.id.fullscreen_button);
+ final ImageButton splitscreenBtn = windowingPillView.findViewById(
+ R.id.split_screen_button);
+ final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+ final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
+ fullscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ splitscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ floatingBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ desktopBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ final ColorStateList activeColorStateList = ColorStateList.valueOf(
+ mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
+ final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
+ mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
+ fullscreenBtn.setImageTintList(
+ mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ ? activeColorStateList : inActiveColorStateList);
+ splitscreenBtn.setImageTintList(
+ mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ ? activeColorStateList : inActiveColorStateList);
+ floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
+ ? activeColorStateList : inActiveColorStateList);
+ desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ ? activeColorStateList : inActiveColorStateList);
}
- mDragResizeListener.close();
- mDragResizeListener = null;
+
+ // More Actions pill setup.
+ final View moreActionsPillView = mHandleMenuMoreActionsPill.mWindowViewHost.getView();
+ final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
+ closeBtn.setOnClickListener(mOnCaptionButtonClickListener);
}
/**
- * Create and display handle menu window
+ * Updates the handle menu pills' position variables to reflect their next positions
*/
- void createHandleMenu() {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- final Resources resources = mDecorWindowContext.getResources();
+ private void updateHandleMenuPillPositions() {
+ final int menuX, menuY;
final int captionWidth = mTaskInfo.getConfiguration()
.windowConfiguration.getBounds().width();
- final int menuWidth = loadDimensionPixelSize(resources, mHandleMenuWidthId);
- final int menuHeight = loadDimensionPixelSize(resources, mCaptionMenuHeightId);
- final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
- final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
-
- final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
- mHandleMenuPosition.set(x, y);
- String namePrefix = "Caption Menu";
- mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
- menuWidth, menuHeight, shadowRadius, cornerRadius);
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
- setupHandleMenu();
+ if (mRelayoutParams.mLayoutResId
+ == R.layout.desktop_mode_app_controls_window_decor) {
+ // Align the handle menu to the left of the caption.
+ menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
+ } else {
+ // Position the handle menu at the center of the caption.
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
+ }
+
+ // App Info pill setup.
+ final int appInfoPillY = menuY;
+ mHandleMenuAppInfoPillPosition.set(menuX, appInfoPillY);
+
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+
+ final int windowingPillY, moreActionsPillY;
+ if (shouldShowWindowingPill) {
+ windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+ mHandleMenuWindowingPillPosition.set(menuX, windowingPillY);
+ moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
+ mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
+ } else {
+ // Just start after the end of the app info pill + margins.
+ moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+ mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
+ }
}
/**
@@ -377,8 +450,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
- mHandleMenu.releaseView();
- mHandleMenu = null;
+ mHandleMenuAppInfoPill.releaseView();
+ mHandleMenuAppInfoPill = null;
+ if (mHandleMenuWindowingPill != null) {
+ mHandleMenuWindowingPill.releaseView();
+ mHandleMenuWindowingPill = null;
+ }
+ mHandleMenuMoreActionsPill.releaseView();
+ mHandleMenuMoreActionsPill = null;
}
@Override
@@ -397,12 +476,33 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// When this is called before the layout is fully inflated, width will be 0.
// Menu is not visible in this scenario, so skip the check if that is the case.
- if (mHandleMenu.mWindowViewHost.getView().getWidth() == 0) return;
+ if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
PointF inputPoint = offsetCaptionLocation(ev);
- if (!pointInView(mHandleMenu.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuPosition.x - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuPosition.y - mResult.mDecorContainerOffsetY)) {
+
+ // If this is called before open_menu_button's onClick, we don't want to close
+ // the menu since it will just reopen in onClick.
+ final boolean pointInOpenMenuButton = pointInView(
+ mResult.mRootView.findViewById(R.id.open_menu_button),
+ inputPoint.x,
+ inputPoint.y);
+
+ final boolean pointInAppInfoPill = pointInView(
+ mHandleMenuAppInfoPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+ inputPoint.y - mHandleMenuAppInfoPillPosition.y);
+ boolean pointInWindowingPill = false;
+ if (mHandleMenuWindowingPill != null) {
+ pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuWindowingPillPosition.x,
+ inputPoint.y - mHandleMenuWindowingPillPosition.y);
+ }
+ final boolean pointInMoreActionsPill = pointInView(
+ mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+ inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
+ if (!pointInAppInfoPill && !pointInWindowingPill
+ && !pointInMoreActionsPill && !pointInOpenMenuButton) {
closeHandleMenu();
}
}
@@ -459,14 +559,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final View handle = caption.findViewById(R.id.caption_handle);
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
- final View menu = mHandleMenu.mWindowViewHost.getView();
- final int captionWidth = mTaskInfo.getConfiguration().windowConfiguration
- .getBounds().width();
- final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2)
- - (menu.getWidth() / 2);
- final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY());
- final View collapse = menu.findViewById(R.id.collapse_menu_button);
- if (clickIfPointInView(inputPoint, collapse)) return;
+ final View appInfoPill = mHandleMenuAppInfoPill.mWindowViewHost.getView();
+ final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
+ // Translate the input point from display coordinates to the same space as the collapse
+ // button, meaning its parent (app info pill view).
+ final PointF inputPoint = new PointF(ev.getX() - mHandleMenuAppInfoPillPosition.x,
+ ev.getY() - mHandleMenuAppInfoPillPosition.y);
+ clickIfPointInView(inputPoint, collapse);
}
}
@@ -490,6 +589,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
super.close();
}
+ private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
+ if (DesktopModeStatus.isProto1Enabled()) {
+ return R.layout.desktop_mode_app_controls_window_decor;
+ }
+ return windowingMode == WINDOWING_MODE_FREEFORM
+ ? R.layout.desktop_mode_app_controls_window_decor
+ : R.layout.desktop_mode_focused_window_decor;
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index cf1850b92373..65b5a7a17afe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -56,10 +56,15 @@ class DragDetector {
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
*/
boolean onMotionEvent(MotionEvent ev) {
+ final boolean isTouchScreen =
+ (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ if (!isTouchScreen) {
+ // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
+ // to take the slop threshold into consideration.
+ return mEventHandler.handleMotionEvent(ev);
+ }
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
- // Only touch screens generate noisy moves.
- mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
@@ -72,8 +77,12 @@ class DragDetector {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ // Touches generate noisy moves, so only once the move is past the touch
+ // slop threshold should it be considered a drag.
mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
}
+ // The event handler should only be notified about 'move' events if a drag has been
+ // detected.
if (mIsDragEvent) {
return mEventHandler.handleMotionEvent(ev);
} else {
@@ -94,10 +103,6 @@ class DragDetector {
mTouchSlop = touchSlop;
}
- boolean isDragEvent() {
- return mIsDragEvent;
- }
-
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
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 0a9c3310a883..d5437c72acac 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
@@ -64,8 +64,8 @@ class DragResizeInputListener implements AutoCloseable {
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
- private int mWidth;
- private int mHeight;
+ private int mTaskWidth;
+ private int mTaskHeight;
private int mResizeHandleThickness;
private int mCornerSize;
@@ -103,6 +103,7 @@ class DragResizeInputListener implements AutoCloseable {
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
+ 0 /* inputFeatures */,
TYPE_APPLICATION,
null /* windowToken */,
mFocusGrantToken,
@@ -127,78 +128,84 @@ class DragResizeInputListener implements AutoCloseable {
* This is also used to update the touch regions of this handler every event dispatched here is
* a potential resize request.
*
- * @param width The width of the drag resize handler in pixels, including resize handle
- * thickness. That is task width + 2 * resize handle thickness.
- * @param height The height of the drag resize handler in pixels, including resize handle
- * thickness. That is task height + 2 * resize handle thickness.
+ * @param taskWidth The width of the task.
+ * @param taskHeight The height of the task.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
* @param cornerSize The size of the resize handle centered in each corner.
* @param touchSlop The distance in pixels user has to drag with touch for it to register as
* a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
int touchSlop) {
- if (mWidth == width && mHeight == height
+ if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
&& mResizeHandleThickness == resizeHandleThickness
&& mCornerSize == cornerSize) {
return;
}
- mWidth = width;
- mHeight = height;
+ mTaskWidth = taskWidth;
+ mTaskHeight = taskHeight;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
- final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ final Rect topInputBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskWidth + mResizeHandleThickness,
+ 0);
touchRegion.union(topInputBounds);
- final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
- mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ final Rect leftInputBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskHeight);
touchRegion.union(leftInputBounds);
final Rect rightInputBounds = new Rect(
- mWidth - mResizeHandleThickness, mResizeHandleThickness,
- mWidth, mHeight - mResizeHandleThickness);
+ mTaskWidth,
+ 0,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight);
touchRegion.union(rightInputBounds);
- final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
- mWidth, mHeight);
+ final Rect bottomInputBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskHeight,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight + mResizeHandleThickness);
touchRegion.union(bottomInputBounds);
// Set up touch areas in each corner.
int cornerRadius = mCornerSize / 2;
mLeftTopCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
touchRegion.union(mLeftTopCornerBounds);
mRightTopCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ -cornerRadius,
+ mTaskWidth + cornerRadius,
+ cornerRadius);
touchRegion.union(mRightTopCornerBounds);
mLeftBottomCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ mTaskHeight - cornerRadius,
+ cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mLeftBottomCornerBounds);
mRightBottomCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ mTaskHeight - cornerRadius,
+ mTaskWidth + cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mRightBottomCornerBounds);
try {
@@ -208,6 +215,7 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
+ 0 /* inputFeatures */,
touchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -356,16 +364,16 @@ class DragResizeInputListener implements AutoCloseable {
@TaskPositioner.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
- if (x < mResizeHandleThickness) {
+ if (x < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
}
- if (x > mWidth - mResizeHandleThickness) {
+ if (x > mTaskWidth) {
ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
}
- if (y < mResizeHandleThickness) {
+ if (y < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
}
- if (y > mHeight - mResizeHandleThickness) {
+ if (y > mTaskHeight) {
ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
}
return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
new file mode 100644
index 000000000000..4417209b85ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
@@ -0,0 +1 @@
+jorgegil@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index a3d364a0068e..0bce3acecb3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback {
private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
+ private final Rect mTempBounds = new Rect();
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
@@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback {
final float deltaX = x - mRepositionStartPoint.x;
final float deltaY = y - mRepositionStartPoint.y;
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+
+ final Rect stableBounds = mTempBounds;
+ // Make sure the new resizing destination in any direction falls within the stable bounds.
+ // If not, set the bounds back to the old location that was valid to avoid conflicts with
+ // some regions such as the gesture area.
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(stableBounds);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- mRepositionTaskBounds.left += deltaX;
+ final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
+ mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
+ ? candidateLeft : oldLeft;
}
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- mRepositionTaskBounds.right += deltaX;
+ final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
+ mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
+ ? candidateRight : oldRight;
}
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- mRepositionTaskBounds.top += deltaY;
+ final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
+ mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
+ ? candidateTop : oldTop;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- mRepositionTaskBounds.bottom += deltaY;
+ final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
+ mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
+ ? candidateBottom : oldBottom;
}
if (mCtrlType == CTRL_TYPE_UNDEFINED) {
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 3734487e8f4b..9f03d9aec166 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
+import android.os.IBinder;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -95,4 +97,34 @@ public interface WindowDecorViewModel {
* @param taskInfo the info of the task
*/
void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+
+ /**
+ * Notifies that a shell transition is about to start. If the transition is of type
+ * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
+ * task after the transition has ended.
+ *
+ * @param transition the ready transaction
+ * @param info of Transition to check if relayout needs to be paused for a task
+ * @param change a change in the given transition
+ */
+ default void onTransitionReady(IBinder transition, TransitionInfo info,
+ TransitionInfo.Change change) {}
+
+ /**
+ * Notifies that a shell transition is about to merge with another to give the window
+ * decoration a chance to prepare for this merge.
+ *
+ * @param merged the transaction being merged
+ * @param playing the transaction being merged into
+ */
+ default void onTransitionMerged(IBinder merged, IBinder playing) {}
+
+ /**
+ * Notifies that a shell transition is about to finish to give the window decoration a chance
+ * to clean up.
+ *
+ * @param transaction
+ */
+ default void onTransitionFinished(IBinder transaction) {}
+
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7a7ac476879e..9a1b4ffbd50c 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
@@ -24,15 +24,17 @@ import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Binder;
import android.view.Display;
-import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.window.SurfaceSyncGroup;
import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
@@ -58,7 +60,6 @@ import java.util.function.Supplier;
*/
public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
implements AutoCloseable {
- private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR };
/**
* System-wide context. Only used to create context with overridden configurations.
@@ -84,19 +85,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
};
RunningTaskInfo mTaskInfo;
+ int mLayoutResId;
final SurfaceControl mTaskSurface;
Display mDisplay;
Context mDecorWindowContext;
SurfaceControl mDecorationContainerSurface;
- SurfaceControl mTaskBackgroundSurface;
SurfaceControl mCaptionContainerSurface;
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
+ private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
- private final Rect mTaskSurfaceCrop = new Rect();
private final float[] mTmpColor = new float[3];
WindowDecoration(
@@ -131,7 +132,17 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+ mDecorWindowContext = mContext.createConfigurationContext(
+ getConfigurationWithOverrides(mTaskInfo));
+ }
+
+ /**
+ * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+ *
+ * Allows values to be overridden before returning the configuration.
+ */
+ protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+ return taskInfo.getConfiguration();
}
/**
@@ -152,6 +163,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
+ final int oldLayoutResId = mLayoutResId;
+ mLayoutResId = params.mLayoutResId;
if (!mTaskInfo.isVisible) {
releaseViews();
@@ -165,10 +178,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = mTaskInfo.getConfiguration();
+ final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
- || mDisplay.getDisplayId() != mTaskInfo.displayId) {
+ || mDisplay.getDisplayId() != mTaskInfo.displayId
+ || oldLayoutResId != mLayoutResId) {
releaseViews();
if (!obtainDisplayOrRegisterListener()) {
@@ -178,15 +192,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
if (params.mLayoutResId != 0) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId, null);
+ .inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId , null);
+ .inflate(params.mLayoutResId, null);
}
+ final Resources resources = mDecorWindowContext.getResources();
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ outResult.mWidth = taskBounds.width();
+ outResult.mHeight = taskBounds.height();
+
// DecorationContainerSurface
if (mDecorationContainerSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -201,46 +220,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
}
- final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- final Resources resources = mDecorWindowContext.getResources();
- outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
- outResult.mWidth = taskBounds.width()
- + loadDimensionPixelSize(resources, params.mOutsetRightId)
- - outResult.mDecorContainerOffsetX;
- outResult.mHeight = taskBounds.height()
- + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - outResult.mDecorContainerOffsetY;
- startT.setPosition(
- mDecorationContainerSurface,
- outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface,
- outResult.mWidth, outResult.mHeight)
+ startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
- // TaskBackgroundSurface
- if (mTaskBackgroundSurface == null) {
- final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
- mTaskBackgroundSurface = builder
- .setName("Background of Task=" + mTaskInfo.taskId)
- .setEffectLayer()
- .setParent(mTaskSurface)
- .build();
-
- startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
- }
-
- float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
- int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
- mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
- mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
- taskBounds.height())
- .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
- .setColor(mTaskBackgroundSurface, mTmpColor)
- .show(mTaskBackgroundSurface);
-
// CaptionContainerSurface, CaptionWindowManager
if (mCaptionContainerSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -253,14 +235,35 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
-
- startT.setPosition(
- mCaptionContainerSurface,
- -outResult.mDecorContainerOffsetX + params.mCaptionX,
- -outResult.mDecorContainerOffsetY + params.mCaptionY)
- .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
+ if (ViewRootImpl.CAPTION_ON_SHELL) {
+ outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
+
+ // Caption insets
+ mCaptionInsetsRect.set(taskBounds);
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ } else {
+ startT.hide(mCaptionContainerSurface);
+ }
+
+ // Task surface itself
+ float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ Point taskPosition = mTaskInfo.positionInParent;
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
+ .setShadowRadius(mTaskSurface, shadowRadius)
+ .setColor(mTaskSurface, mTmpColor)
+ .show(mTaskSurface);
+ finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
@@ -280,34 +283,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
mCaptionWindowManager);
+ if (params.mApplyStartTransactionOnDraw) {
+ mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
+ }
mViewHost.setView(outResult.mRootView, lp);
} else {
+ if (params.mApplyStartTransactionOnDraw) {
+ mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
+ }
mViewHost.relayout(lp);
}
-
- if (ViewRootImpl.CAPTION_ON_SHELL) {
- outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
-
- // Caption insets
- mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
- wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
- CAPTION_INSETS_TYPES);
- } else {
- startT.hide(mCaptionContainerSurface);
- }
-
- // Task surface itself
- Point taskPosition = mTaskInfo.positionInParent;
- mTaskSurfaceCrop.set(
- outResult.mDecorContainerOffsetX,
- outResult.mDecorContainerOffsetY,
- outResult.mWidth + outResult.mDecorContainerOffsetX,
- outResult.mHeight + outResult.mDecorContainerOffsetY);
- startT.show(mTaskSurface);
- finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setCrop(mTaskSurface, mTaskSurfaceCrop);
}
/**
@@ -347,18 +332,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
released = true;
}
- if (mTaskBackgroundSurface != null) {
- t.remove(mTaskBackgroundSurface);
- mTaskBackgroundSurface = null;
- released = true;
- }
-
if (released) {
t.apply();
}
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
- wct.removeInsetsProvider(mTaskInfo.token, CAPTION_INSETS_TYPES);
+ wct.removeInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar());
mTaskOrganizer.applyTransaction(wct);
}
@@ -385,18 +365,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
- * @param layoutId layout to make the window from
- * @param t the transaction to apply
- * @param xPos x position of new window
- * @param yPos y position of new window
- * @param width width of new window
- * @param height height of new window
+ *
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
* @param shadowRadius radius of the shadow of the new window
* @param cornerRadius radius of the corners of the new window
* @return the {@link AdditionalWindow} that was added.
*/
AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
- int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
+ SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
+ int cornerRadius) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -420,37 +402,22 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
windowSurfaceControl, null /* hostInputToken */);
SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
.create(mDecorWindowContext, mDisplay, windowManager);
- viewHost.setView(v, lp);
+ ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
return new AdditionalWindow(windowSurfaceControl, viewHost,
mSurfaceControlTransactionSupplier);
}
- static class RelayoutParams{
+ static class RelayoutParams {
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
int mCaptionHeightId;
int mCaptionWidthId;
int mShadowRadiusId;
- int mOutsetTopId;
- int mOutsetBottomId;
- int mOutsetLeftId;
- int mOutsetRightId;
-
int mCaptionX;
int mCaptionY;
- void setOutsets(int leftId, int topId, int rightId, int bottomId) {
- mOutsetLeftId = leftId;
- mOutsetTopId = topId;
- mOutsetRightId = rightId;
- mOutsetBottomId = bottomId;
- }
-
- void setCaptionPosition(int left, int top) {
- mCaptionX = left;
- mCaptionY = top;
- }
+ boolean mApplyStartTransactionOnDraw;
void reset() {
mLayoutResId = Resources.ID_NULL;
@@ -458,13 +425,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
- mOutsetTopId = Resources.ID_NULL;
- mOutsetBottomId = Resources.ID_NULL;
- mOutsetLeftId = Resources.ID_NULL;
- mOutsetRightId = Resources.ID_NULL;
-
mCaptionX = 0;
mCaptionY = 0;
+
+ mApplyStartTransactionOnDraw = false;
}
}
@@ -472,14 +436,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mWidth;
int mHeight;
T mRootView;
- int mDecorContainerOffsetX;
- int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
- mDecorContainerOffsetX = 0;
- mDecorContainerOffsetY = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
new file mode 100644
index 000000000000..b67acd5c15bb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -0,0 +1,84 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
+ * finer controls such as a close window button and an "app info" section to pull up additional
+ * controls.
+ */
+internal class DesktopModeAppControlsWindowDecorationViewHolder(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: View.OnClickListener,
+ appName: CharSequence,
+ appIcon: Drawable
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+ private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+ private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
+ private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
+ private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
+ private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
+ private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
+ private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+
+ init {
+ captionView.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnTouchListener(onCaptionTouchListener)
+ openMenuButton.setOnClickListener(onCaptionButtonClickListener)
+ openMenuButton.setOnTouchListener(onCaptionTouchListener)
+ closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ closeWindowButton.setOnTouchListener(onCaptionTouchListener)
+ appNameTextView.text = appName
+ appIconImageView.setImageDrawable(appIcon)
+ }
+
+ override fun bindData(taskInfo: RunningTaskInfo) {
+
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+
+ closeWindowButton.imageTintList = ColorStateList.valueOf(
+ getCaptionCloseButtonColor(taskInfo))
+ expandMenuButton.imageTintList = ColorStateList.valueOf(
+ getCaptionExpandButtonColor(taskInfo))
+ appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
+ }
+
+ private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_app_name_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_app_name_dark)
+ }
+ }
+
+ private fun getCaptionCloseButtonColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_close_button_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_close_button_dark)
+ }
+ }
+
+ private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_expand_button_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_expand_button_dark)
+ }
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
new file mode 100644
index 000000000000..47a12a0cb71c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -0,0 +1,44 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.widget.ImageButton
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
+ * hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
+ */
+internal class DesktopModeFocusedWindowDecorationViewHolder(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: View.OnClickListener
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+ private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+ private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+
+ init {
+ captionView.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnClickListener(onCaptionButtonClickListener)
+ }
+
+ override fun bindData(taskInfo: RunningTaskInfo) {
+ val captionColor = taskInfo.taskDescription.statusBarColor
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(captionColor)
+
+ captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+ }
+
+ private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_handle_bar_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
new file mode 100644
index 000000000000..514ea52cb8ae
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -0,0 +1,28 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.view.View
+
+/**
+ * Encapsulates the root [View] of a window decoration and its children to facilitate looking up
+ * children (via findViewById) and updating to the latest data from [RunningTaskInfo].
+ */
+internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
+ val context: Context = rootView.context
+
+ /**
+ * A signal to the view holder that new data is available and that the views should be updated
+ * to reflect it.
+ */
+ abstract fun bindData(taskInfo: RunningTaskInfo)
+
+ /**
+ * Whether the caption items should use the 'light' color variant so that there's good contrast
+ * with the caption background color.
+ */
+ protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+ return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 1c28c3d58ccb..64dfc3ef845d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -7,3 +7,4 @@ lbill@google.com
madym@google.com
hwwang@google.com
chenghsiuchang@google.com
+atsjenk@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 67ca9a1a17f7..b6d92814ad43 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -29,6 +29,7 @@
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
<option name="teardown-command" value="settings delete system show_touches" />
<option name="teardown-command" value="settings delete system pointer_location" />
+ <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ed93045ec462..c416ad011c4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -137,7 +137,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible(
portraitPosTop: Boolean
) {
assertLayers {
- this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
.then()
@@ -217,14 +217,23 @@ fun FlickerTest.splitAppLayerBoundsChanges(
) {
assertLayers {
if (landscapePosLeft) {
- this.splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
} else {
- this.splitAppLayerBoundsSnapToDivider(
+ splitAppLayerBoundsSnapToDivider(
component,
landscapePosLeft,
portraitPosTop,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
new file mode 100644
index 000000000000..d01a0ee67f25
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.system.helpers.CommandsHelper
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import com.android.wm.shell.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.IFlickerTestData
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import org.junit.Assume
+import org.junit.Before
+import org.junit.runners.Parameterized
+
+abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxApp = LetterboxAppHelper(instrumentation)
+ lateinit var cmdHelper: CommandsHelper
+ lateinit var letterboxStyle: HashMap<String, String>
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ letterboxApp.launchViaIntent(wmHelper)
+ setEndRotation()
+ }
+ }
+
+ @Before
+ fun before() {
+ cmdHelper = CommandsHelper.getInstance(instrumentation)
+ Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun isIgnoreOrientationRequest(): Boolean {
+ val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
+ return res != null && res.contains("true")
+ }
+
+ fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
+
+ fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
+
+ /** Checks that app entering letterboxed state have rounded corners */
+ fun assertLetterboxAppAtStartHasRoundedCorners() {
+ assumeLetterboxRoundedCornersEnabled()
+ flicker.assertLayersStart { this.hasRoundedCorners(letterboxApp) }
+ }
+
+ fun assertLetterboxAppAtEndHasRoundedCorners() {
+ assumeLetterboxRoundedCornersEnabled()
+ flicker.assertLayersEnd { this.hasRoundedCorners(letterboxApp) }
+ }
+
+ /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
+ private fun assumeLetterboxRoundedCornersEnabled() {
+ if (!::letterboxStyle.isInitialized) {
+ letterboxStyle = mapLetterboxStyle()
+ }
+ Assume.assumeTrue(letterboxStyle.getValue("Corner radius") != "0")
+ }
+
+ fun assertLetterboxAppVisibleAtStartAndEnd() {
+ flicker.appWindowIsVisibleAtStart(letterboxApp)
+ flicker.appWindowIsVisibleAtEnd(letterboxApp)
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
new file mode 100644
index 000000000000..c57100e44c17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ *
+ * Actions:
+ * ```
+ * Rotate non resizable portrait only app to opposite orientation to trigger size compat mode
+ * ```
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ letterboxApp.launchViaIntent(wmHelper)
+ }
+ transitions { setEndRotation() }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ /**
+ * Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
+ * focus returns to [letterboxApp]
+ */
+ @Postsubmit
+ @Test
+ fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) }
+
+ @Postsubmit
+ @Test
+ fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+
+ /**
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
+ * flicker, and disappears before the transition is complete
+ */
+ @Postsubmit
+ @Test
+ fun rotationLayerAppearsAndVanishes() {
+ flicker.assertLayers {
+ this.isVisible(letterboxApp)
+ .then()
+ .isVisible(ComponentNameMatcher.ROTATION)
+ .then()
+ .isVisible(letterboxApp)
+ .isInvisible(ComponentNameMatcher.ROTATION)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
new file mode 100644
index 000000000000..f111a8d62d83
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.helpers.WindowUtils
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test restarting app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ *
+ * Actions:
+ * ```
+ * Rotate app to opposite orientation to trigger size compat mode
+ * Press restart button and wait for letterboxed app to resize
+ * ```
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ transitions { letterboxApp.clickRestart(wmHelper) }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ @Postsubmit
+ @Test
+ fun appVisibleAtStartAndEnd() = assertLetterboxAppVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ fun appLayerVisibilityChanges() {
+ flicker.assertLayers {
+ this.isVisible(letterboxApp)
+ .then()
+ .isInvisible(letterboxApp)
+ .then()
+ .isVisible(letterboxApp)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtStartHasRoundedCorners()
+
+ /** Checks that the visible region of [letterboxApp] is still within display bounds */
+ @Postsubmit
+ @Test
+ fun appWindowRemainInsideVisibleBounds() {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
+ flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index d0bca1332553..2474ecf74cf9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -26,8 +26,6 @@ import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import org.junit.Assume
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -45,13 +43,8 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FlakyTest(bugId = 217777115)
open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
-
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
deleted file mode 100644
index 5e85eb87e0e9..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
+++ /dev/null
@@ -1,39 +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.flicker.bubble
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FlakyTest(bugId = 217777115)
-class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
- ChangeActiveActivityFromBubbleTest(flicker) {
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 416315e4b06d..18a3aa79f6e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
+import android.tools.common.datatypes.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -29,6 +30,7 @@ import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import org.junit.Assume
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -137,4 +139,23 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleSc
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
super.navBarWindowIsAlwaysVisible()
}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. Taskbar is not shown on lock screen")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. Taskbar is not shown on lock screen")
+ override fun taskBarWindowIsAlwaysVisible() {}
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] is visible at the end of the transition
+ */
+ @Postsubmit
+ @Test
+ fun taskBarLayerIsVisibleAtEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 1045a5ac2ce8..93ee6992a98f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -40,6 +40,7 @@ import org.junit.runners.Parameterized
* Select "Auto-enter PiP" radio button
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
* ```
+ *
* Notes:
* ```
* 1. All assertions are inherited from [EnterPipTest]
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 2d2588ef4348..59918fb7b6a9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -38,6 +38,7 @@ import org.junit.runners.Parameterized
* Launch an app in pip mode [pipApp],
* Swipe the pip window to the bottom-center of the screen and wait it disappear
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index 6c5a344c8f79..36c6f7c438c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -43,25 +42,17 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker)
@Presubmit
@Test
open fun pipWindowBecomesInvisible() {
- if (isShellTransitionsEnabled) {
- // When Shell transition is enabled, we change the windowing mode at start, but
- // update the visibility after the transition is finished, so we can't check isNotPinned
- // and isAppWindowInvisible in the same assertion block.
- flicker.assertWm {
- this.invoke("hasPipWindow") {
- it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
- }
- .then()
- .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
- }
- flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
- } else {
- flicker.assertWm {
- this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
- .then()
- .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
- }
+ // When Shell transition is enabled, we change the windowing mode at start, but
+ // update the visibility after the transition is finished, so we can't check isNotPinned
+ // and isAppWindowInvisible in the same assertion block.
+ flicker.assertWm {
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
+ }
+ .then()
+ .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
}
+ flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index e540ad543228..d16583271e8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -38,6 +38,7 @@ import org.junit.runners.Parameterized
* Click on the pip window
* Click on dismiss button and wait window disappear
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 11bb0cc1306e..f52e877ec2b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -74,10 +74,18 @@ open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransitio
@Presubmit
@Test
- override fun pipAppLayerOrOverlayAlwaysVisible() {
+ override fun pipAppLayerAlwaysVisible() {
// pip layer in gesture nav will disappear during transition
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
- super.pipAppLayerOrOverlayAlwaysVisible()
+ super.pipAppLayerAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // no overlay in gesture nav for non-auto enter PiP transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipOverlayLayerAppearThenDisappear()
}
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index e079d5477e2f..db18edba9cc4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -53,6 +53,7 @@ import org.junit.runners.Parameterized
* Launch [pipApp] on a fixed landscape orientation
* Broadcast action [ACTION_ENTER_PIP] to enter pip mode
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 327225421580..51f01364ec9c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -43,13 +43,17 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker)
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
- open fun pipAppLayerOrOverlayAlwaysVisible() {
+ open fun pipAppLayerAlwaysVisible() {
+ flicker.assertLayers { this.isVisible(pipApp) }
+ }
+
+ /** Checks the content overlay appears then disappears during the animation */
+ @Presubmit
+ @Test
+ open fun pipOverlayLayerAppearThenDisappear() {
+ val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
flicker.assertLayers {
- this.isVisible(pipApp)
- .then()
- .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
- .then()
- .isVisible(pipApp)
+ this.notContains(overlay).then().contains(overlay).then().notContains(overlay)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 1f060e931be2..f1925d8c9d85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -35,6 +35,7 @@ import org.junit.runners.Parameterized
* Launch an app in full screen
* Press an "enter pip" button to put [pipApp] in pip mode
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 313631cbe8ee..3e0e37dfc997 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,16 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import org.junit.Assume
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -42,6 +37,7 @@ import org.junit.runners.Parameterized
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -72,19 +68,4 @@ open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppT
wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
-
- @Presubmit
- @Test
- fun pipLayerExpands_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 93ffdd8d5294..603f99541a12 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -16,16 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import org.junit.Assume
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -41,6 +36,7 @@ import org.junit.runners.Parameterized
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen via an intent
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -71,37 +67,4 @@ open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransit
wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
-
- /** {@inheritDoc} */
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- @Presubmit
- @Test
- fun statusBarLayerRotatesScales_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
-
- @Presubmit
- @Test
- fun pipLayerExpands_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 7d5f740838bd..6deba1b68f38 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -40,6 +40,7 @@ import org.junit.runners.Parameterized
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 0b73aac02797..d860e00fbfff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
+ get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } }
/** Checks that the visible region area of [pipApp] always increases during the animation. */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 9c007449fb8d..d8d57d219933 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -40,6 +40,7 @@ import org.junit.runners.Parameterized
* Launch [testApp]
* Check if pip window moves down (visually)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index c23838a987bf..a626713aaa11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -28,8 +27,6 @@ import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.Assume.assumeFalse
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,11 +41,6 @@ import org.junit.runners.Parameterized
open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
- @Before
- open fun before() {
- assumeFalse(isShellTransitionsEnabled)
- }
-
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
deleted file mode 100644
index 6f8111690f0f..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
+++ /dev/null
@@ -1,47 +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.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestShellTransit(flicker: FlickerTest) :
- MovePipOnImeVisibilityChangeTest(flicker) {
-
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index c8d5624b1d77..ae3f87967658 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -40,6 +40,7 @@ import org.junit.runners.Parameterized
* Press home
* Check if pip window moves up (visually)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
new file mode 100644
index 000000000000..4e2a4e700698
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test the dragging of a PIP window. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
+ private var isDraggedLeft: Boolean = true
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+
+ setup {
+ tapl.setEnableRotation(true)
+ // Launch the PIP activity and wait for it to enter PiP mode
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+
+ // determine the direction of dragging to test for
+ isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ teardown {
+ // release the primary pointer after dragging without release
+ pipApp.releasePipAfterDragging()
+
+ pipApp.exit(wmHelper)
+ tapl.setEnableRotation(false)
+ }
+ transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
+ }
+
+ @Postsubmit
+ @Test
+ fun pipLayerMovesAwayFromEdge() {
+ flicker.assertLayers {
+ val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ if (isDraggedLeft) {
+ previous.visibleRegion.isToTheRight(current.visibleRegion.region)
+ } else {
+ current.visibleRegion.isToTheRight(previous.visibleRegion.region)
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
new file mode 100644
index 000000000000..9fe9f52fd4af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.graphics.Rect
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
+ // represents the direction in which the pip window should be snapping
+ private var willSnapRight: Boolean = true
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ val stringExtras: Map<String, String> =
+ mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+
+ // cache the starting bounds here
+ val startBounds = Rect()
+
+ setup {
+ // Launch the PIP activity and wait for it to enter PiP mode
+ setRotation(Rotation.ROTATION_0)
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+
+ // get the initial region bounds and cache them
+ val initRegion = pipApp.getWindowRect(wmHelper)
+ startBounds.set(
+ initRegion.left,
+ initRegion.top,
+ initRegion.right,
+ initRegion.bottom
+ )
+
+ // drag the pip window away from the edge
+ pipApp.dragPipWindowAwayFromEdge(wmHelper, 50)
+
+ // determine the direction in which the snapping should occur
+ willSnapRight = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ transitions {
+ // continue the transition until the PIP snaps
+ pipApp.waitForPipToSnapTo(wmHelper, startBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region area of [pipApp] moves to closest edge during the animation.
+ */
+ @Postsubmit
+ @Test
+ fun pipLayerMovesToClosestEdge() {
+ flicker.assertLayers {
+ val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ if (willSnapRight) {
+ current.visibleRegion.isToTheRight(previous.visibleRegion.region)
+ } else {
+ previous.visibleRegion.isToTheRight(current.visibleRegion.region)
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 85b2fbce2f21..8eb41b4ac694 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -34,6 +35,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 270677470)
class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 2cf8f61f13fe..703784dd8c67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -43,6 +43,7 @@ import org.junit.runners.Parameterized
* Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index bd2ffc1a018d..2e81b30d2e9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -71,7 +71,7 @@ class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
+ fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(secondaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 7db5ecc484ad..5180791276a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -86,16 +85,14 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Presubmit
@Test
- fun primaryAppLayerKeepVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerKeepVisible(primaryApp)
- }
-
- @FlakyTest(bugId = 263213649)
- @Test
- fun primaryAppLayerKeepVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerKeepVisible(primaryApp)
+ fun primaryAppLayerVisibilityChanges() {
+ flicker.assertLayers {
+ this.isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ }
}
@Presubmit
@@ -116,21 +113,9 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Test
fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun primaryAppBoundsChanges() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.splitAppLayerBoundsChanges(
- primaryApp,
- landscapePosLeft = true,
- portraitPosTop = false
- )
- }
-
- @FlakyTest(bugId = 263213649)
- @Test
- fun primaryAppBoundsChanges_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.splitAppLayerBoundsChanges(
primaryApp,
landscapePosLeft = true,
@@ -147,11 +132,6 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
portraitPosTop = true
)
- /** {@inheritDoc} */
- @FlakyTest(bugId = 263213649)
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index ffdb87f190d5..69da1e29a19c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -79,20 +79,24 @@ class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 792e2b03522f..1773846c18e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -82,20 +82,19 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
@@ -104,23 +103,6 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.assertLayers {
- this.isInvisible(sendNotificationApp)
- .then()
- .isVisible(sendNotificationApp)
- .then()
- .isInvisible(sendNotificationApp)
- .then()
- .isVisible(sendNotificationApp)
- }
- }
-
- // TODO(b/245472831): Align to legacy transition after shell transition ready.
- @Presubmit
- @Test
- fun secondaryAppLayerBecomesVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.layerBecomesVisible(sendNotificationApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index da80c6f46976..3bea66ef0a27 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -80,20 +80,24 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
@@ -102,23 +106,6 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.assertLayers {
- this.isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- .then()
- .isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- }
- }
-
- // TODO(b/245472831): Align to legacy transition after shell transition ready.
- @Presubmit
- @Test
- fun secondaryAppLayerBecomesVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.layerBecomesVisible(secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 7901f7502e2c..62936e0f5ca8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -293,7 +293,7 @@ internal object SplitScreenUtils {
wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
+ dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 2000)
wmHelper
.StateSyncBuilder()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 2855c71518eb..9f4cb8c381fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -20,7 +20,6 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@ import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,19 +71,7 @@ class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker)
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @FlakyTest
- @Test
- fun primaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
-
- @Presubmit
- @Test
- fun primaryAppLayerBecomesVisibleShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index c29a917c4e7c..a33d8cab9fbd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -20,7 +20,6 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@ import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,19 +71,7 @@ class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicke
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @FlakyTest
- @Test
- fun primaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
-
- @Presubmit
- @Test
- fun primaryAppLayerBecomesVisibleShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 2ac1dc0c4838..57a698128d77 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -69,6 +69,8 @@ android_test {
enabled: false,
},
+ test_suites: ["device-tests"],
+
platform_apis: true,
certificate: "platform",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
index 35c374ddd974..a658375ca38a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -30,6 +30,7 @@ import android.window.TransitionInfo;
*/
public class TransitionInfoBuilder {
final TransitionInfo mInfo;
+ static final int DISPLAY_ID = 0;
public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
this(type, 0 /* flags */);
@@ -37,8 +38,15 @@ public class TransitionInfoBuilder {
public TransitionInfoBuilder(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
+ this(type, flags, false /* asNoOp */);
+ }
+
+ public TransitionInfoBuilder(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags, boolean asNoOp) {
mInfo = new TransitionInfo(type, flags);
- mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+ if (!asNoOp) {
+ mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
+ }
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +69,7 @@ public class TransitionInfoBuilder {
}
public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
mInfo.addChange(change);
return this;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index cbbb29199d75..b8f615a1855f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -82,10 +83,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
+ final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
+ final TransitionInfo.Change embeddedOpen = createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
+ nonEmbeddedOpen.setMode(TRANSIT_OPEN);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createEmbeddedChange(
- EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
- .addChange(createChange(0 /* flags */))
+ .addChange(embeddedOpen)
+ .addChange(nonEmbeddedOpen)
.build();
// No-op because it contains non-embedded change.
@@ -95,6 +99,22 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mStartTransaction);
verifyNoMoreInteractions(mFinishTransaction);
verifyNoMoreInteractions(mFinishCallback);
+
+ final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */);
+ nonEmbeddedClose.setMode(TRANSIT_CLOSE);
+ nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS);
+ final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange(
+ EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS);
+ final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(embeddedOpen)
+ .addChange(embeddedOpen2)
+ .addChange(nonEmbeddedClose)
+ .build();
+ // Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2.
+ assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ // The non-embedded change is dropped to avoid affecting embedded animation.
+ assertFalse(info2.getChanges().contains(nonEmbeddedClose));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 5a4a44fde58d..3d8bd3854a45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellExecutor.flushAll();
}
- private void createNavigationInfo(int backType, boolean enableAnimation) {
+ private void createNavigationInfo(int backType,
+ boolean enableAnimation,
+ boolean isAnimationCallback) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
.setOnBackInvokedCallback(mAppCallback)
- .setPrepareRemoteAnimation(enableAnimation);
+ .setPrepareRemoteAnimation(enableAnimation)
+ .setAnimationCallback(isAnimationCallback);
createNavigationInfo(builder);
}
@@ -166,6 +169,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 0);
mController.setTriggerBack(true);
+ }
+
+ private void releaseBackGesture() {
doMotionEvent(MotionEvent.ACTION_UP, 0);
}
@@ -201,6 +207,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
simulateRemoteAnimationStart(type);
+ mShellExecutor.flushAll();
+ releaseBackGesture();
simulateRemoteAnimationFinished();
mShellExecutor.flushAll();
@@ -213,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
@@ -235,6 +245,32 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ true);
+
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+
+ // Check that back start and progress is dispatched when first move.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000);
+
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
+ verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verify(mAnimatorCallback).onBackInvoked();
+ }
+
+ @Test
public void animationDisabledFromSettings() throws RemoteException {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
@@ -249,12 +285,13 @@ public class BackAnimationControllerTest extends ShellTestCase {
ArgumentCaptor<BackMotionEvent> backEventCaptor =
ArgumentCaptor.forClass(BackMotionEvent.class);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ false,
+ /* isAnimationCallback = */ false);
triggerBackGesture();
+ releaseBackGesture();
- verify(mAppCallback, never()).onBackStarted(any());
- verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mAppCallback, times(1)).onBackInvoked();
verify(mAnimatorCallback, never()).onBackStarted(any());
@@ -267,10 +304,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void ignoresGesture_transitionInProgress() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ releaseBackGesture();
+
// Check that back invocation is dispatched.
verify(mAnimatorCallback).onBackInvoked();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -303,13 +344,18 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
// In case it is still running in animation.
doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ mShellExecutor.flushAll();
+
+ releaseBackGesture();
// Simulate transition timeout.
mShellExecutor.flushAll();
@@ -325,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
@@ -371,6 +419,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
simulateRemoteAnimationStart(type);
mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
+
assertTrue("Navigation Done callback not called for "
+ BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
assertTrue("TriggerBack should have been true", result.mTriggerBack);
@@ -397,6 +448,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
assertTrue("Navigation Done callback not called for "
+ BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
@@ -411,18 +464,74 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mAnimatorCallback, never()).onBackInvoked();
}
+ @Test
+ public void testBackToActivity() throws RemoteException {
+ final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ }
+
+ @Test
+ public void testBackToTask() throws RemoteException {
+ final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ }
+
+ private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
+ throws RemoteException {
+ final BackAnimationRunner animationRunner = spy(animation);
+ final IRemoteAnimationRunner runner = spy(animationRunner.getRunner());
+ final IOnBackInvokedCallback callback = spy(animationRunner.getCallback());
+
+ // Set up the monitoring objects.
+ doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(runner).when(animationRunner).getRunner();
+ doReturn(callback).when(animationRunner).getCallback();
+
+ mController.registerAnimation(type, animationRunner);
+
+ createNavigationInfo(type,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
+
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+
+ // Check that back start and progress is dispatched when first move.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+ simulateRemoteAnimationStart(type);
+
+ verify(callback).onBackStarted(any(BackMotionEvent.class));
+ verify(animationRunner).startAnimation(any(), any(), any(), any());
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verify(callback).onBackInvoked();
+ }
+
private void doMotionEvent(int actionDown, int coordinate) {
+ doMotionEvent(actionDown, coordinate, 0);
+ }
+
+ private void doMotionEvent(int actionDown, int coordinate, float velocity) {
mController.onMotionEvent(
- coordinate, coordinate,
- actionDown,
- BackEvent.EDGE_LEFT);
+ /* touchX */ coordinate,
+ /* touchY */ coordinate,
+ /* velocityX = */ velocity,
+ /* velocityY = */ velocity,
+ /* keyAction */ actionDown,
+ /* swipeEdge */ BackEvent.EDGE_LEFT);
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {
RemoteAnimationTarget animationTarget = createAnimationTarget();
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
- mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
+ mController.mBackAnimationAdapter.getRunner().onAnimationStart(
targets, null, null, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 3608474bd90e..874ef80c29f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.back;
-import static android.window.BackEvent.EDGE_LEFT;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -48,12 +46,21 @@ public class BackProgressAnimatorTest {
private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
private Handler mMainThreadHandler;
+ private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
+ return new BackMotionEvent(
+ /* touchX = */ touchX,
+ /* touchY = */ 0,
+ /* progress = */ progress,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT,
+ /* departingAnimationTarget = */ null);
+ }
+
@Before
public void setUp() throws Exception {
mMainThreadHandler = new Handler(Looper.getMainLooper());
- final BackMotionEvent backEvent = new BackMotionEvent(
- 0, 0,
- 0, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
() -> {
mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackProgressed() throws InterruptedException {
- final BackMotionEvent backEvent = new BackMotionEvent(
- 100, 0,
- mTargetProgress, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
@@ -78,9 +83,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackCancelled() throws InterruptedException {
// Give the animator some progress.
- final BackMotionEvent backEvent = new BackMotionEvent(
- 100, 0,
- mTargetProgress, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
mTargetProgressCalled.await(1, TimeUnit.SECONDS);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
new file mode 100644
index 000000000000..e7d459893ce8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.WindowConfiguration;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.BackNavigationInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class CustomizeActivityAnimationTest extends ShellTestCase {
+ private static final int BOUND_SIZE = 100;
+ @Mock
+ private BackAnimationBackground mBackAnimationBackground;
+ @Mock
+ private Animation mMockCloseAnimation;
+ @Mock
+ private Animation mMockOpenAnimation;
+
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
+ @Before
+ public void setUp() throws Exception {
+ mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
+ mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
+ mock(Choreographer.class));
+ spyOn(mCustomizeActivityAnimation);
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
+ }
+
+ RemoteAnimationTarget createAnimationTarget(boolean open) {
+ SurfaceControl topWindowLeash = new SurfaceControl();
+ return new RemoteAnimationTarget(1,
+ open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
+ topWindowLeash, false, new Rect(), new Rect(), -1,
+ new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
+ new WindowConfiguration(), true, null, null, null, false, -1);
+ }
+
+ @Test
+ public void receiveFinishAfterInvoke() throws InterruptedException {
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(true));
+
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishAfterCancel() throws InterruptedException {
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(true));
+
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ } catch (RemoteException r) {
+ fail("onBackCancelled throw remote exception");
+ }
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ // start animation without any remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{}, null, null, finishCallback);
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testLoadCustomAnimation() {
+ testLoadCustomAnimation(10, 20, 0);
+ }
+
+ @Test
+ public void testLoadCustomAnimationNoEnter() {
+ testLoadCustomAnimation(0, 10, 0);
+ }
+
+ @Test
+ public void testLoadWindowAnimations() {
+ testLoadCustomAnimation(0, 0, 30);
+ }
+
+ @Test
+ public void testCustomAnimationHigherThanWindowAnimations() {
+ testLoadCustomAnimation(10, 20, 30);
+ }
+
+ private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
+ final String testPackage = "TestPackage";
+ BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
+ .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
+ .setWindowAnimations(testPackage, windowAnimations);
+ final BackNavigationInfo.CustomAnimationInfo info = builder.build()
+ .getCustomAnimationInfo();
+
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
+
+ CustomizeActivityAnimation.AnimationLoadResult result =
+ mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
+
+ if (exitResId != 0) {
+ if (enterResId == 0) {
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+ never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
+ .loadDefaultAnimationAttr(anyInt(), anyBoolean());
+ } else {
+ assertEquals(result.mEnterAnimation, mMockOpenAnimation);
+ }
+ assertEquals(result.mBackgroundColor, Color.GREEN);
+ assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
+ .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+ } else if (windowAnimations != 0) {
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+ times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+ assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index ba9c159bad28..d62e6601723a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -47,43 +47,45 @@ public class TouchTrackerTest {
public void generatesProgress_leftEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
float touchX = 10;
+ float velocityX = 0;
+ float velocityY = 0;
// Pre-commit
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX += 100;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Cancel
touchX -= 10;
mTouchTracker.setTriggerBack(false);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX += 10;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
}
@@ -91,43 +93,45 @@ public class TouchTrackerTest {
public void generatesProgress_rightEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+ float velocityX = 0f;
+ float velocityY = 0f;
// Pre-commit
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX -= 100;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Cancel
touchX += 10;
mTouchTracker.setTriggerBack(false);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX -= 10;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 8b025cd7c246..4a55429eacb6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -185,7 +183,11 @@ public class BubbleDataTest extends ShellTestCase {
Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
appBubbleIntent.setPackage(mContext.getPackageName());
- mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor);
+ mAppBubble = Bubble.createAppBubble(
+ appBubbleIntent,
+ new UserHandle(1),
+ mock(Icon.class),
+ mMainExecutor);
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
@@ -1100,14 +1102,15 @@ public class BubbleDataTest extends ShellTestCase {
@Test
public void test_removeAppBubble_skipsOverflow() {
+ String appBubbleKey = mAppBubble.getKey();
mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
false /* showInShade */);
- assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble);
+ assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isEqualTo(mAppBubble);
- mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE);
+ mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull();
- assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
private void verifyUpdateReceived() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index e8f3f69ca64e..de967bfa288b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -29,6 +29,8 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -162,4 +164,27 @@ public class BubbleTest extends ShellTestCase {
verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
}
+
+ @Test
+ public void testBubbleIsConversation_hasConversationShortcut() {
+ Bubble bubble = createBubbleWithShortcut();
+ assertThat(bubble.getShortcutInfo()).isNotNull();
+ assertThat(bubble.isConversation()).isTrue();
+ }
+
+ @Test
+ public void testBubbleIsConversation_hasNoShortcut() {
+ Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
+ assertThat(bubble.getShortcutInfo()).isNull();
+ assertThat(bubble.isConversation()).isFalse();
+ }
+
+ private Bubble createBubbleWithShortcut() {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("mockShortcutId")
+ .build();
+ return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL,
+ "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
+ mMainExecutor, mBubbleMetadataFlagListener);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 000000000000..96d202ce3a85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}
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 bc0d93a6810a..a6501f05475f 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
@@ -54,7 +54,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index a58620dfc6dc..172c263ab0f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -58,9 +58,8 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLayout = (LetterboxEduDialogLayout)
- LayoutInflater.from(mContext).inflate(R.layout.letterbox_education_dialog_layout,
- null);
+ mLayout = (LetterboxEduDialogLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.letterbox_education_dialog_layout, null);
mDismissButton = mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button);
mDialogContainer = mLayout.findViewById(R.id.letterbox_education_dialog_container);
mLayout.setDismissOnClickListener(mDismissCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 14190f18929c..12ceb0a9a9ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -31,14 +31,12 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
-import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.SurfaceControlViewHost;
@@ -53,10 +51,10 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -68,6 +66,10 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
/**
* Tests for {@link LetterboxEduWindowManager}.
*
@@ -81,8 +83,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
- private static final String PREF_KEY_1 = String.valueOf(USER_ID_1);
- private static final String PREF_KEY_2 = String.valueOf(USER_ID_2);
+ private static final String TEST_COMPAT_UI_SHARED_PREFERENCES = "test_compat_ui_configuration";
+
+ private static final String TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES =
+ "test_has_seen_letterbox";
private static final int TASK_ID = 1;
@@ -104,46 +108,46 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@Mock private Transitions mTransitions;
- @Mock private Runnable mOnDismissCallback;
+ @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
@Mock private DockStateReader mDockStateReader;
- private SharedPreferences mSharedPreferences;
- @Nullable
- private Boolean mInitialPrefValue1 = null;
- @Nullable
- private Boolean mInitialPrefValue2 = null;
+ private CompatUIConfiguration mCompatUIConfiguration;
+ private TestShellExecutor mExecutor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mSharedPreferences = mContext.getSharedPreferences(
- LetterboxEduWindowManager.HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
- Context.MODE_PRIVATE);
- if (mSharedPreferences.contains(PREF_KEY_1)) {
- mInitialPrefValue1 = mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false);
- mSharedPreferences.edit().remove(PREF_KEY_1).apply();
- }
- if (mSharedPreferences.contains(PREF_KEY_2)) {
- mInitialPrefValue2 = mSharedPreferences.getBoolean(PREF_KEY_2, /* default= */ false);
- mSharedPreferences.edit().remove(PREF_KEY_2).apply();
- }
+ mExecutor = new TestShellExecutor();
+ mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
+
+ final Set<Integer> mHasSeenSet = new HashSet<>();
+
+ @Override
+ boolean getHasSeenLetterboxEducation(int userId) {
+ return mHasSeenSet.contains(userId);
+ }
+
+ @Override
+ void setSeenLetterboxEducation(int userId) {
+ mHasSeenSet.add(userId);
+ }
+
+ @Override
+ protected String getCompatUISharedPreferenceName() {
+ return TEST_COMPAT_UI_SHARED_PREFERENCES;
+ }
+
+ @Override
+ protected String getHasSeenLetterboxEducationSharedPreferencedName() {
+ return TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES;
+ }
+ };
}
@After
public void tearDown() {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- if (mInitialPrefValue1 == null) {
- editor.remove(PREF_KEY_1);
- } else {
- editor.putBoolean(PREF_KEY_1, mInitialPrefValue1);
- }
- if (mInitialPrefValue2 == null) {
- editor.remove(PREF_KEY_2);
- } else {
- editor.putBoolean(PREF_KEY_2, mInitialPrefValue2);
- }
- editor.apply();
+ mContext.deleteSharedPreferences(TEST_COMPAT_UI_SHARED_PREFERENCES);
+ mContext.deleteSharedPreferences(TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES);
}
@Test
@@ -167,8 +171,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
- LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
- true, USER_ID_1, /* isTaskbarEduShowing= */ true);
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ true);
assertFalse(windowManager.createLayout(/* canShow= */ true));
@@ -181,7 +185,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertTrue(windowManager.createLayout(/* canShow= */ false));
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
assertNull(windowManager.mLayout);
}
@@ -202,7 +206,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
spyOn(dialogTitle);
// The education shouldn't be marked as seen until enter animation is done.
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
// Clicking the layout does nothing until enter animation is done.
layout.performClick();
verify(mAnimationController, never()).startExitAnimation(any(), any());
@@ -211,7 +215,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
verifyAndFinishEnterAnimation(layout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
verify(dialogTitle).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
// Exit animation should start following a click on the layout.
layout.performClick();
@@ -219,13 +223,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
// Window manager isn't released until exit animation is done.
verify(windowManager, never()).release();
+ // After dismissed the user has seen the dialog
+ assertTrue(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
+
// Verify multiple clicks are ignored.
layout.performClick();
verifyAndFinishExitAnimation(layout);
verify(windowManager).release();
- verify(mOnDismissCallback).run();
+ verify(mOnDismissCallback).accept(any());
}
@Test
@@ -237,7 +244,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertNotNull(windowManager.mLayout);
verifyAndFinishEnterAnimation(windowManager.mLayout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+
+ // We dismiss
+ windowManager.mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button)
+ .performClick();
windowManager.release();
windowManager = createWindowManager(/* eligible= */ true,
@@ -255,7 +265,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertNotNull(windowManager.mLayout);
verifyAndFinishEnterAnimation(windowManager.mLayout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertTrue(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
}
@Test
@@ -272,7 +282,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
mRunOnIdleCaptor.getValue().run();
verify(mAnimationController, never()).startEnterAnimation(any(), any());
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
}
@Test
@@ -298,7 +308,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
mTaskListener, /* canShow= */ true));
verify(windowManager).release();
- verify(mOnDismissCallback, never()).run();
+ verify(mOnDismissCallback, never()).accept(any());
verify(mAnimationController, never()).startExitAnimation(any(), any());
assertNull(windowManager.mLayout);
}
@@ -396,23 +406,22 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
- return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
- false, isDocked);
+ return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false, isDocked);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing) {
return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing, boolean isDocked) {
doReturn(isDocked).when(mDockStateReader).isDocked();
- LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
+ LetterboxEduWindowManager
+ windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
- createDisplayLayout(), mTransitions, mOnDismissCallback,
- mAnimationController, mDockStateReader);
-
+ createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
+ mDockStateReader, mCompatUIConfiguration);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
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
new file mode 100644
index 000000000000..4f71b83179b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxEduDialogLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityEduLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ReachabilityEduLayoutTest extends ShellTestCase {
+
+ private ReachabilityEduLayout mLayout;
+ private View mMoveUpButton;
+ private View mMoveDownButton;
+ private View mMoveLeftButton;
+ private View mMoveRightButton;
+
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+
+ @Mock
+ private TaskInfo mTaskInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLayout = (ReachabilityEduLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.reachability_ui_layout, null);
+ mMoveLeftButton = mLayout.findViewById(R.id.reachability_move_left_button);
+ mMoveRightButton = mLayout.findViewById(R.id.reachability_move_right_button);
+ mMoveUpButton = mLayout.findViewById(R.id.reachability_move_up_button);
+ mMoveDownButton = mLayout.findViewById(R.id.reachability_move_down_button);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertNotNull(mMoveUpButton);
+ assertNotNull(mMoveDownButton);
+ assertNotNull(mMoveLeftButton);
+ assertNotNull(mMoveRightButton);
+ }
+
+ @Test
+ public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */
+ -1, /* letterboxHorizontalPosition */ -1, /* availableWidth */
+ 0, /* availableHeight */ 0, mCompatUIConfiguration, mTaskInfo);
+ assertEquals(View.INVISIBLE, mMoveUpButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveDownButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveLeftButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveRightButton.getVisibility());
+ }
+
+ @Test
+ public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
+ 1, /* availableWidth */ 500, /* availableHeight */ 0, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration).setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration, never()).setUserHasSeenVerticalReachabilityEducation(
+ mTaskInfo);
+ }
+
+ @Test
+ public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
+ -1, /* availableWidth */ 0, /* availableHeight */ 500, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration, never())
+ .setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration).setUserHasSeenVerticalReachabilityEducation(mTaskInfo);
+ }
+}
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
new file mode 100644
index 000000000000..5bcc72e73cb9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ReachabilityEduWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityEduWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ReachabilityEduWindowManagerTest extends ShellTestCase {
+
+ private static final int USER_ID = 1;
+ private static final int TASK_ID = 1;
+
+ @Mock
+ private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock
+ private CompatUIController.CompatUICallback mCallback;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+ @Mock
+ private DisplayLayout mDisplayLayout;
+
+ private TestShellExecutor mExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ }
+
+ @Test
+ public void testCreateLayout_notEligible_doesNotCreateLayout() {
+ final ReachabilityEduWindowManager windowManager = createReachabilityEduWindowManager(
+ createTaskInfo(/* userId= */ USER_ID, /*isLetterboxDoubleTapEnabled */ false));
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
+ return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+ }
+
+ private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled) {
+ return createTaskInfo(userId, /* isLetterboxDoubleTapEnabled */ isLetterboxDoubleTapEnabled,
+ /* topActivityLetterboxVerticalPosition */ -1,
+ /* topActivityLetterboxHorizontalPosition */ -1,
+ /* topActivityLetterboxWidth */ -1,
+ /* topActivityLetterboxHeight */ -1);
+ }
+
+ private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled,
+ int topActivityLetterboxVerticalPosition, int topActivityLetterboxHorizontalPosition,
+ int topActivityLetterboxWidth, int topActivityLetterboxHeight) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.userId = userId;
+ taskInfo.taskId = TASK_ID;
+ taskInfo.isLetterboxDoubleTapEnabled = isLetterboxDoubleTapEnabled;
+ taskInfo.topActivityLetterboxVerticalPosition = topActivityLetterboxVerticalPosition;
+ taskInfo.topActivityLetterboxHorizontalPosition = topActivityLetterboxHorizontalPosition;
+ taskInfo.topActivityLetterboxWidth = topActivityLetterboxWidth;
+ taskInfo.topActivityLetterboxHeight = topActivityLetterboxHeight;
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 43f8f7b074bf..63de74fa3b05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -41,6 +41,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
@@ -418,6 +419,17 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(wct).isNotNull();
}
+ @Test
+ public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
+ RunningTaskInfo trigger = new RunningTaskInfo();
+ trigger.token = new MockToken().token();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mController.handleRequest(
+ mock(IBinder.class),
+ new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
+ verifyZeroInteractions(mTransitions);
+ }
+
private DesktopModeController createController() {
return new DesktopModeController(mContext, mShellInit, mShellController,
mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
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 95e78a8b7bcc..c9bd695ffb33 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
@@ -37,11 +37,14 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -73,8 +76,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellController: ShellController
+ @Mock lateinit var displayController: DisplayController
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var syncQueue: SyncTransactionQueue
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
+ @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
+ @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
lateinit var mockitoSession: StaticMockitoSession
lateinit var controller: DesktopTasksController
@@ -105,8 +113,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
context,
shellInit,
shellController,
+ displayController,
shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
transitions,
+ enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler,
desktopModeTaskRepository,
TestShellExecutor()
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
new file mode 100644
index 000000000000..6199e0b05059
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.transition.Transitions;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Supplier;
+
+/** Tests of {@link com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler} */
+@SmallTest
+public class EnterDesktopTaskTransitionHandlerTest {
+
+ @Mock
+ private Transitions mTransitions;
+ @Mock
+ IBinder mToken;
+ @Mock
+ Supplier<SurfaceControl.Transaction> mTransactionFactory;
+ @Mock
+ SurfaceControl.Transaction mStartT;
+ @Mock
+ SurfaceControl.Transaction mFinishT;
+ @Mock
+ SurfaceControl.Transaction mAnimationT;
+ @Mock
+ Transitions.TransitionFinishCallback mTransitionFinishCallback;
+ @Mock
+ ShellExecutor mExecutor;
+ @Mock
+ SurfaceControl mSurfaceControl;
+
+ private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mExecutor).when(mTransitions).getMainExecutor();
+ doReturn(mAnimationT).when(mTransactionFactory).get();
+
+ mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
+ mTransactionFactory);
+ }
+
+ @Test
+ public void testEnterFreeformAnimation() {
+ final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
+ final int taskId = 1;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ doReturn(mToken).when(mTransitions)
+ .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
+ mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+
+ TransitionInfo.Change change =
+ createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+
+
+ assertTrue(mEnterDesktopTaskTransitionHandler
+ .startAnimation(mToken, info, mStartT, mFinishT, mTransitionFinishCallback));
+
+ verify(mStartT).setWindowCrop(mSurfaceControl, null);
+ verify(mStartT).apply();
+ }
+
+ @Test
+ public void testTransitEnterDesktopModeAnimation() throws Throwable {
+ final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+ final int taskId = 1;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ doReturn(mToken).when(mTransitions)
+ .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
+ mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+
+ TransitionInfo.Change change =
+ createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
+ change.setEndAbsBounds(new Rect(0, 0, 1, 1));
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+
+ runOnUiThread(() -> {
+ try {
+ assertTrue(mEnterDesktopTaskTransitionHandler
+ .startAnimation(mToken, info, mStartT, mFinishT,
+ mTransitionFinishCallback));
+ } catch (Exception e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ });
+
+ verify(mStartT).setWindowCrop(mSurfaceControl, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+ verify(mStartT).apply();
+ }
+
+ private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)), mSurfaceControl);
+ change.setMode(type);
+ change.setTaskInfo(taskInfo);
+ return change;
+ }
+
+ private static TransitionInfo createTransitionInfo(
+ @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
+ TransitionInfo info = new TransitionInfo(type, 0);
+ info.addChange(change);
+ return info;
+ }
+
+}
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
new file mode 100644
index 000000000000..2c5a5cd72c53
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
+@SmallTest
+public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Transitions mTransitions;
+ @Mock
+ IBinder mToken;
+ @Mock
+ Supplier<SurfaceControl.Transaction> mTransactionFactory;
+ @Mock
+ Context mContext;
+ @Mock
+ DisplayMetrics mDisplayMetrics;
+ @Mock
+ Resources mResources;
+ @Mock
+ SurfaceControl.Transaction mStartT;
+ @Mock
+ SurfaceControl.Transaction mFinishT;
+ @Mock
+ SurfaceControl.Transaction mAnimationT;
+ @Mock
+ Transitions.TransitionFinishCallback mTransitionFinishCallback;
+ @Mock
+ ShellExecutor mExecutor;
+
+ private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mExecutor).when(mTransitions).getMainExecutor();
+ doReturn(mAnimationT).when(mTransactionFactory).get();
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+ when(mResources.getDisplayMetrics())
+ .thenReturn(getContext().getResources().getDisplayMetrics());
+
+ mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
+ mContext);
+ }
+
+ @Test
+ public void testTransitExitDesktopModeAnimation() throws Throwable {
+ final int transitionType = Transitions.TRANSIT_EXIT_DESKTOP_MODE;
+ final int taskId = 1;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ doReturn(mToken).when(mTransitions)
+ .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
+
+ mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+
+ TransitionInfo.Change change =
+ createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_EXIT_DESKTOP_MODE, change);
+ ArrayList<Exception> exceptions = new ArrayList<>();
+ runOnUiThread(() -> {
+ try {
+ assertTrue(mExitDesktopTaskTransitionHandler
+ .startAnimation(mToken, info, mStartT, mFinishT,
+ mTransitionFinishCallback));
+ } catch (Exception e) {
+ exceptions.add(e);
+ }
+ });
+ if (!exceptions.isEmpty()) {
+ throw exceptions.get(0);
+ }
+ }
+
+ private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ SurfaceControl.Builder b = new SurfaceControl.Builder()
+ .setName("test task");
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ taskInfo.token, b.build());
+ change.setMode(type);
+ change.setTaskInfo(taskInfo);
+ return change;
+ }
+
+ private static TransitionInfo createTransitionInfo(
+ @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
+ TransitionInfo info = new TransitionInfo(type, 0);
+ info.addChange(change);
+ return info;
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index ecfb427dbced..58e91cb50c7a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -77,6 +78,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private ShellInit mShellInit;
@Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
+ @Mock private Resources mResources;
KidsModeTaskOrganizer mOrganizer;
@@ -89,10 +91,12 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
- mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
- mTestExecutor, mHandler));
+ doReturn(mResources).when(mContext).getResources();
+ final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
+ mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
+ mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
+ mObserver, mTestExecutor, mHandler);
+ mOrganizer = spy(kidsModeTaskOrganizer);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@@ -112,6 +116,8 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
verify(mOrganizer, times(1)).registerOrganizer();
verify(mOrganizer, times(1)).createRootTask(
eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(true), any(), any());
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
@@ -132,10 +138,11 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
doReturn(false).when(mObserver).isEnabled();
mOrganizer.updateKidsModeState();
-
verify(mOrganizer, times(1)).disable();
verify(mOrganizer, times(1)).unregisterOrganizer();
verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(false), any(), any());
assertThat(mOrganizer.mLaunchRootLeash).isNull();
assertThat(mOrganizer.mLaunchRootTask).isNull();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index ec264a643785..addc2338144f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -55,25 +55,28 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private static final float MAX_ASPECT_RATIO = 2f;
private static final int DEFAULT_MIN_EDGE_SIZE = 100;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
- private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
}
private void initializeMockResources() {
@@ -88,6 +91,9 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
R.dimen.default_minimal_size_pip_resizable_task,
DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+ res.addOverride(
R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
res.addOverride(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 341a451eeb43..f32000445ca9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -27,11 +27,13 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.util.Size;
import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
@@ -52,13 +54,23 @@ public class PipBoundsStateTest extends ShellTestCase {
private static final Size DEFAULT_SIZE = new Size(10, 10);
private static final float DEFAULT_SNAP_FRACTION = 1.0f;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsState mPipBoundsState;
private ComponentName mTestComponentName1;
private ComponentName mTestComponentName2;
@Before
public void setUp() {
- mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+
+ PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipBoundsState = new PipBoundsState(mContext,
+ new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e907cd3ca0ad..842c699fa42d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -88,6 +88,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -97,15 +98,16 @@ public class PipTaskOrganizerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
- mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
+ mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
@@ -259,9 +261,9 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final DisplayInfo info = new DisplayInfo();
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+ .takeOneShotEnterAnimationType();
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
}
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 4a68287a4486..6995d10dd78d 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
@@ -53,12 +53,14 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -108,11 +110,13 @@ public class PipControllerTest extends ShellTestCase {
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock private TabletopModeController mMockTabletopModeController;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -130,11 +134,12 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
@@ -221,11 +226,12 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
mMockOneHandedController, mMockExecutor));
}
@@ -283,8 +289,8 @@ public class PipControllerTest extends ShellTestCase {
when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1));
when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE));
when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(true);
@@ -299,8 +305,8 @@ public class PipControllerTest extends ShellTestCase {
final int displayId = 1;
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(false);
@@ -312,9 +318,10 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
+ mPipController.setEnablePipKeepClearAlgorithm(false);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
@@ -327,7 +334,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.setEnablePipKeepClearAlgorithm(true);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
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 c7b9eb3d1074..ada3455fae18 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
@@ -37,6 +37,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -87,11 +88,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
@@ -155,7 +159,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be bigger than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
@@ -194,7 +198,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be smaller than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index d9ff7d1f1089..425bbf056b85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@@ -33,6 +32,7 @@ import android.view.DisplayInfo;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
import org.junit.Assert;
@@ -74,7 +74,8 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Mock private Context mContext;
@Mock private Resources mResources;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+ private TestPipSizeSpecHandler mPipSizeSpecHandler;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -82,8 +83,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
private static void setUpStaticSystemPropertiesSession() {
sStaticMockitoSession = mockitoSession()
.mockStatic(SystemProperties.class).startMocking();
- // make sure the feature flag is on
- when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
String property = invocation.getArgument(0);
if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
@@ -137,7 +136,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Before
public void setUp() {
initExpectedSizes();
- setUpStaticSystemPropertiesSession();
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
@@ -148,11 +146,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
-
- // no overridden min edge size by default
- mPipSizeSpecHandler.setOverrideMinSize(null);
-
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
@@ -161,7 +154,14 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
false, false);
- mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+
+ setUpStaticSystemPropertiesSession();
+ mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+
+ // no overridden min edge size by default
+ mPipSizeSpecHandler.setOverrideMinSize(null);
}
@After
@@ -211,4 +211,16 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
Assert.assertEquals(expectedSize, actualSize);
}
+
+ static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
+
+ TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
+ super(context, displayLayoutState);
+ }
+
+ @Override
+ boolean supportsPipSizeLargeScreen() {
+ return true;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 5c4863ff752f..10b1ddf1b868 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
@@ -35,6 +35,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -92,6 +93,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
@@ -105,8 +107,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
@@ -124,8 +127,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler);
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipBoundsState.setDisplayLayout(mDisplayLayout);
- mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
+ mPipDisplayLayoutState.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET,
@@ -173,6 +175,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Test
public void updateMovementBounds_withImeAdjustment_movesPip() {
+ mPipTouchHandler.setEnablePipKeepClearAlgorithm(false);
mFromImeAdjustment = true;
mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index e5b61ed2fd25..02e6b8c71663 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -158,19 +158,73 @@ public class TvPipActionProviderTest extends ShellTestCase {
new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
}
- @Test
- public void expandedPip_toggleExpansion() {
- assumeTelevision();
- // PiP has expanded PiP enabled, but is in a collapsed state
+ private void check_expandedPip_updateExpansionState(
+ boolean startExpansion, boolean endExpansion, boolean updateExpected) {
+
mActionsProvider.updateExpansionEnabled(true);
- mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+ mActionsProvider.updatePipExpansionState(startExpansion);
mActionsProvider.addListener(mMockListener);
- mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+ mActionsProvider.updatePipExpansionState(endExpansion);
assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
- verify(mMockListener).onActionsChanged(0, 1, 3);
+
+ if (updateExpected) {
+ verify(mMockListener).onActionsChanged(0, 1, 3);
+ } else {
+ verify(mMockListener, times(0))
+ .onActionsChanged(anyInt(), anyInt(), anyInt());
+ }
+ }
+
+ @Test
+ public void expandedPip_toggleExpansion_collapse() {
+ assumeTelevision();
+ check_expandedPip_updateExpansionState(
+ /* startExpansion= */ true,
+ /* endExpansion= */ false,
+ /* updateExpected= */ true);
+ }
+
+ @Test
+ public void expandedPip_toggleExpansion_expand() {
+ assumeTelevision();
+ check_expandedPip_updateExpansionState(
+ /* startExpansion= */ false,
+ /* endExpansion= */ true,
+ /* updateExpected= */ true);
+ }
+
+ @Test
+ public void expandedPiP_updateExpansionState_alreadyExpanded() {
+ assumeTelevision();
+ check_expandedPip_updateExpansionState(
+ /* startExpansion= */ true,
+ /* endExpansion= */ true,
+ /* updateExpected= */ false);
+ }
+
+ @Test
+ public void expandedPiP_updateExpansionState_alreadyCollapsed() {
+ assumeTelevision();
+ check_expandedPip_updateExpansionState(
+ /* startExpansion= */ false,
+ /* endExpansion= */ false,
+ /* updateExpected= */ false);
+ }
+
+ @Test
+ public void regularPiP_updateExpansionState_setCollapsed() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.updatePipExpansionState(/* expanded= */ false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updatePipExpansionState(/* expanded= */ false);
+
+ verify(mMockListener, times(0))
+ .onActionsChanged(anyInt(), anyInt(), anyInt());
}
@Test
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 30096cb99a64..f9b772345b14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertEquals;
import android.view.Gravity;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
@@ -47,6 +48,7 @@ public class TvPipGravityTest extends ShellTestCase {
private TvPipBoundsState mTvPipBoundsState;
private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
public void setUp() {
@@ -54,8 +56,10 @@ public class TvPipGravityTest extends ShellTestCase {
return;
}
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+ mPipDisplayLayoutState);
mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
mMockPipSnapAlgorithm, mPipSizeSpecHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
new file mode 100644
index 000000000000..3a08d32bc430
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SystemWindows;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class TvPipMenuControllerTest extends ShellTestCase {
+ private static final int TEST_MOVE_KEYCODE = KEYCODE_DPAD_UP;
+
+ @Mock
+ private TvPipMenuController.Delegate mMockDelegate;
+ @Mock
+ private TvPipBoundsState mMockTvPipBoundsState;
+ @Mock
+ private SystemWindows mMockSystemWindows;
+ @Mock
+ private SurfaceControl mMockPipLeash;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private TvPipActionsProvider mMockActionsProvider;
+ @Mock
+ private TvPipMenuView mMockTvPipMenuView;
+ @Mock
+ private TvPipBackgroundView mMockTvPipBackgroundView;
+
+ private TvPipMenuController mTvPipMenuController;
+
+ @Before
+ public void setUp() {
+ assumeTrue(isTelevision());
+
+ MockitoAnnotations.initMocks(this);
+
+ mTvPipMenuController = new TestTvPipMenuController();
+ mTvPipMenuController.setDelegate(mMockDelegate);
+ mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
+ mTvPipMenuController.attach(mMockPipLeash);
+ }
+
+ @Test
+ public void testMenuNotOpenByDefault() {
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToMoveMode() {
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromMoveMode_ToAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromAllActionsMode_ToMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testCloseMenu_NoMenuMode() {
+ mTvPipMenuController.closeMenu();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveMode() {
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_NoMenuMode() {
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testExitMoveMode_MoveMode() {
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ }
+
+ @Test
+ public void testOnBackPress_NoMenuMode() {
+ mTvPipMenuController.onBackPress();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveMode() {
+ showAndAssertMoveMenu();
+
+ pressBackAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onBackPress();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipMovement_NoMenuMode() {
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_MoveMode() {
+ showAndAssertMoveMenu();
+ assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_NoMenuMode() {
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_MoveMode() {
+ showAndAssertMoveMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ private void showAndAssertMoveMenu() {
+ mTvPipMenuController.showMovementMenu();
+ assertMenuIsInMoveMode();
+ verify(mMockDelegate).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ }
+
+ private void showAndAssertAllActionsMenu() {
+ mTvPipMenuController.showMenu();
+ assertMenuIsInAllActionsMode();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ }
+
+ private void closeMenuAndAssertMenuClosed() {
+ mTvPipMenuController.closeMenu();
+ assertMenuClosed();
+ }
+
+ private void pressBackAndAssertMenuClosed() {
+ mTvPipMenuController.onBackPress();
+ assertMenuClosed();
+ }
+
+ private void assertMenuClosed() {
+ assertMenuIsOpen(false);
+ verify(mMockDelegate).onMenuClosed();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU));
+ }
+
+ private void assertMenuIsOpen(boolean open) {
+ assertTrue("The TV PiP menu should " + (open ? "" : "not ") + "be open, but it"
+ + " is in mode " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isMenuOpen() == open);
+ }
+
+ private void assertMenuIsInMoveMode() {
+ assertTrue("Expected MODE_MOVE_MENU, but got " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInMoveMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertMenuIsInAllActionsMode() {
+ assertTrue("Expected MODE_ALL_ACTIONS_MENU, but got "
+ + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInAllActionsMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertPipMoveSuccessful(boolean expected, boolean actual) {
+ assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
+ + mTvPipMenuController.getMenuModeString(), expected == actual);
+ }
+
+ private class TestTvPipMenuController extends TvPipMenuController {
+
+ TestTvPipMenuController() {
+ super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+ }
+
+ @Override
+ TvPipMenuView createTvPipMenuView() {
+ return mMockTvPipMenuView;
+ }
+
+ @Override
+ TvPipBackgroundView createTvPipBackgroundView() {
+ return mMockTvPipBackgroundView;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 3901dabcaec8..a9f311f9e9eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -180,7 +181,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null, null);
+ new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Test
@UiThreadTest
- public void testEnterRecents() {
+ public void testEnterRecentsAndCommit() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase {
.build();
// Create a request to bring home forward
- TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
- assertTrue(result.isEmpty());
+ // Don't handle recents opening
+ assertNull(result);
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
- // simulate the transition
- TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
- .addChange(TRANSIT_TO_FRONT, homeTask)
- .addChange(TRANSIT_TO_BACK, mMainChild)
- .addChange(TRANSIT_TO_BACK, mSideChild)
+ // simulate the start of recents transition
+ mMainStage.onTaskVanished(mMainChild);
+ mSideStage.onTaskVanished(mSideChild);
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure it cleans-up if recents doesn't restore
+ WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+ mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class));
+ assertFalse(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnterRecentsAndRestore() {
+ enterSplit();
+
+ ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setActivityType(ACTIVITY_TYPE_HOME)
.build();
+
+ // Create a request to bring home forward
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
+ IBinder transition = mock(IBinder.class);
+ WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+ // Don't handle recents opening
+ assertNull(result);
+
+ // make sure we haven't made any local changes yet (need to wait until transition is ready)
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // simulate the start of recents transition
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
- mStageCoordinator.startAnimation(transition, info,
- mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class),
- mock(Transitions.TransitionFinishCallback.class));
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure we remain in split after recents restores.
+ WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+ restoreWCT.reorder(mMainChild.token, true /* toTop */);
+ restoreWCT.reorder(mSideChild.token, true /* toTop */);
+ // simulate the restoreWCT being applied:
+ mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+ mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+ mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class));
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
@@ -368,7 +407,8 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
+ new RemoteTransition(new TestRemoteTransition(), "Test"),
+ mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2e2e49e40030..e6219d1aa792 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -113,6 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mRootLeash;
+ private SurfaceControl mDividerLeash;
private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
private Transitions mTransitions;
@@ -129,12 +130,14 @@ public class StageCoordinatorTests extends ShellTestCase {
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, Optional.empty()));
+ mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
+ when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -145,39 +148,48 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testMoveToStage() {
+ public void testMoveToStage_splitActiveBackground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+ }
+
+ @Test
+ public void testMoveToStage_splitActiveForeground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
+ // Assume current side stage is top or left.
+ mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
- mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mMainStage).addTask(eq(task), eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
- mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
}
@Test
- public void testMoveToUndefinedStage() {
+ public void testMoveToStage_splitInctive() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Verify move to undefined stage while split screen not activated moves task to side stage.
- when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
- mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
-
- // Verify move to undefined stage after split screen activated moves task based on position.
- when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd28aef..784ad9b006b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -126,12 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
- @Test(expected = IllegalArgumentException.class)
- public void testUnknownTaskVanished() {
- final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- mStageTaskListener.onTaskVanished(task);
- }
-
@Test
public void testTaskVanished() {
// With shell transitions, the transition manages status changes, so skip this test.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 11fda8bf7bbc..bf62acfc47a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -24,8 +24,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -56,11 +56,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
-import android.view.Display;
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
@@ -106,36 +104,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
- TestStartingSurfaceDrawer mStartingSurfaceDrawer;
-
- static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
- int mAddWindowForTask = 0;
-
- TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- IconProvider iconProvider, TransactionPool pool) {
- super(context, splashScreenExecutor, iconProvider, pool);
- }
-
- @Override
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, int suggestType) {
- // listen for addView
- mAddWindowForTask = taskId;
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
- // Do not wait for background color
- return false;
- }
-
- @Override
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo,
- boolean immediately) {
- // listen for removeView
- if (mAddWindowForTask == removalInfo.taskId) {
- mAddWindowForTask = 0;
- }
- mStartingWindowRecords.remove(removalInfo.taskId);
- }
- }
+ StartingSurfaceDrawer mStartingSurfaceDrawer;
private static class TestContext extends TestableContext {
TestContext(Context context) {
@@ -165,44 +134,51 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor,
+ mIconProvider, mTransactionPool);
mStartingSurfaceDrawer = spy(
- new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+ new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
mTransactionPool));
+ spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator);
+ spyOn(mStartingSurfaceDrawer.mWindowRecords);
+ spyOn(mStartingSurfaceDrawer.mWindowlessRecords);
}
@Test
public void testAddSplashScreenSurface() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(),
eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
removalInfo.taskId = windowInfo.taskInfo.taskId;
mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
}
@Test
public void testFallbackDefaultTheme() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, 0);
+ createWindowInfo(taskId, 0, mBinder);
final int[] theme = new int[1];
doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
- .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
assertNotEquals(theme[0], 0);
}
@@ -241,7 +217,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
new Rect(0, 0, 0, 50), true /* hasImeSurface */);
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -270,7 +246,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
any())).thenReturn(mockSnapshotWindow);
// Simulate a task snapshot window created with IME snapshot shown.
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
waitHandlerIdle(mTestHandler);
// Verify the task snapshot with IME snapshot will be removed when received the real IME
@@ -278,27 +254,36 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
// makeTaskSnapshotWindow shall call removeWindowSynced before there add a new
// StartingWindowRecord for the task.
mStartingSurfaceDrawer.onImeDrawnOnTask(1);
- verify(mStartingSurfaceDrawer, times(2))
- .removeWindowSynced(any(), eq(true));
+ verify(mStartingSurfaceDrawer.mWindowRecords, times(2))
+ .removeWindow(any(), eq(true));
}
}
@Test
public void testClearAllWindows() {
final int taskId = 1;
- final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
- STARTING_WINDOW_TYPE_SPLASH_SCREEN);
- waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
- eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
+ mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+
+ }
+ });
+ mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+ }
+ });
mStartingSurfaceDrawer.clearAllWindows();
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true));
- assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
+ verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0);
}
@Test
@@ -351,7 +336,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
longAppDuration, longAppDuration));
}
- private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
+ private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
@@ -360,6 +345,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.topActivityInfo = info;
taskInfo.taskId = taskId;
+ windowInfo.appToken = appToken;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
windowInfo.topOpaqueWindowInsetsState = new InsetsState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 62bfd17cefba..b6d7ff3cd5cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -53,6 +53,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
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 e63bbeb05575..5cd548bfe5ab 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
@@ -62,6 +62,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -99,6 +100,7 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.util.ArrayList;
+import java.util.function.Function;
/**
* Tests for the shell transitions.
@@ -275,7 +277,7 @@ public class ShellTransitionTests extends ShellTestCase {
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
- new RemoteTransition(testRemote)));
+ new RemoteTransition(testRemote, "Test")));
verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
@@ -420,7 +422,7 @@ public class ShellTransitionTests extends ShellTestCase {
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- transitions.registerRemote(filter, new RemoteTransition(testRemote));
+ transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test"));
mMainExecutor.flushAll();
IBinder transitToken = new Binder();
@@ -464,11 +466,12 @@ public class ShellTransitionTests extends ShellTestCase {
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor,
- new RemoteTransition(testRemote));
+ new RemoteTransition(testRemote, "Test"));
// Verify that it responds to the remote but not other things.
IBinder transitToken = new Binder();
assertNotNull(oneShot.handleRequest(transitToken,
- new TransitionRequestInfo(transitType, null, new RemoteTransition(testRemote))));
+ new TransitionRequestInfo(transitType, null,
+ new RemoteTransition(testRemote, "Test"))));
assertNull(oneShot.handleRequest(transitToken,
new TransitionRequestInfo(transitType, null, null)));
@@ -591,6 +594,68 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void testInterleavedMerging() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ Function<Boolean, IBinder> startATransition = (doMerge) -> {
+ IBinder token = new Binder();
+ if (doMerge) {
+ mDefaultHandler.setShouldMerge(token);
+ }
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ return token;
+ };
+
+ IBinder transitToken1 = startATransition.apply(false);
+ // merge first one
+ IBinder transitToken2 = startATransition.apply(true);
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // don't merge next one
+ IBinder transitToken3 = startATransition.apply(false);
+ // make sure nothing happened (since it wasn't merged)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // make a mergable
+ IBinder transitToken4 = startATransition.apply(true);
+ // make sure nothing happened since there is a non-mergable pending.
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // Queue up another mergable
+ IBinder transitToken5 = startATransition.apply(true);
+
+ // Queue up a non-mergable
+ IBinder transitToken6 = startATransition.apply(false);
+
+ // Our active now looks like: [playing, merged]
+ // and ready queue: [non-mergable, mergable, mergable, non-mergable]
+ // finish the playing one
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ // Now we should have the non-mergable playing now with 2 merged:
+ // active: [playing, merged, merged] queue: [non-mergable]
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(2, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ }
+
+ @Test
public void testTransitionOrderMatchesCore() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -975,6 +1040,25 @@ public class ShellTransitionTests extends ShellTestCase {
verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
}
+ @Test
+ public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+
+ // Make a no-op transition
+ TransitionInfo info = new TransitionInfoBuilder(
+ TRANSIT_OPEN, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+ transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ // If keyguard-going-away flag set, then it shouldn't be aborted.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
class ChangeBuilder {
final TransitionInfo.Change mChange;
@@ -1016,6 +1100,7 @@ public class ShellTransitionTests extends ShellTestCase {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
+ final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1030,7 +1115,7 @@ public class ShellTransitionTests extends ShellTestCase {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!mSimulateMerge) return;
+ if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
@@ -1046,12 +1131,23 @@ public class ShellTransitionTests extends ShellTestCase {
mSimulateMerge = sim;
}
+ void setShouldMerge(IBinder toMerge) {
+ mShouldMerge.add(toMerge);
+ }
+
void finishAll() {
final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
}
+ mShouldMerge.clear();
+ }
+
+ void finishOne() {
+ Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+ mMerged.clear();
+ fin.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 1d1aa795173c..9a90996b786c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.WindowConfiguration;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
@@ -60,6 +61,7 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
/** Tests of {@link DesktopModeWindowDecorViewModel} */
@SmallTest
@@ -80,8 +82,9 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
@Mock private DesktopTasksController mDesktopTasksController;
@Mock private InputMonitor mInputMonitor;
@Mock private InputManager mInputManager;
-
@Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
+ @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
+ @Mock private SurfaceControl.Transaction mTransaction;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -102,12 +105,14 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
Optional.of(mDesktopTasksController),
Optional.of(mSplitScreenController),
mDesktopModeWindowDecorFactory,
- mMockInputMonitorFactory
+ mMockInputMonitorFactory,
+ mTransactionFactory
);
doReturn(mDesktopModeWindowDecoration)
.when(mDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), any(), any(), any(), any());
+ doReturn(mTransaction).when(mTransactionFactory).get();
when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
// InputChannel cannot be mocked because it passes to InputEventReceiver.
@@ -250,7 +255,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
}
private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
- int displayId, int windowingMode) {
+ int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
ActivityManager.RunningTaskInfo taskInfo =
new TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index 8f66f4e7e47b..94c064bda763 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -5,13 +5,16 @@ import android.app.WindowConfiguration
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
@@ -19,10 +22,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
/**
@@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() {
private lateinit var mockDisplayController: DisplayController
@Mock
private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
private lateinit var taskPositioner: TaskPositioner
@@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() {
`when`(taskToken.asBinder()).thenReturn(taskBinder)
`when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
`when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
@@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() {
displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
+ mockWindowDecoration.mDisplay = mockDisplay
+ `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@Test
@@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() {
})
}
+ fun testDragResize_toDisallowedBounds_freezesAtLimit() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat()
+ )
+
+ // Resize the task by 10px to the right and bottom, a valid destination
+ val newBounds = Rect(
+ STARTING_BOUNDS.left,
+ STARTING_BOUNDS.top,
+ STARTING_BOUNDS.right + 10,
+ STARTING_BOUNDS.bottom + 10)
+ taskPositioner.onDragPositioningMove(
+ newBounds.right.toFloat(),
+ newBounds.bottom.toFloat()
+ )
+
+ // Resize the task by another 10px to the right (allowed) and to just in the disallowed
+ // area of the Y coordinate.
+ val newBounds2 = Rect(
+ newBounds.left,
+ newBounds.top,
+ newBounds.right + 10,
+ DISALLOWED_RESIZE_AREA.top
+ )
+ taskPositioner.onDragPositioningMove(
+ newBounds2.right.toFloat(),
+ newBounds2.bottom.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
+
+ // The first resize falls in the allowed area, verify there's a change for it.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds)
+ }
+ })
+ // The second resize falls in the disallowed area, verify there's no change for it.
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds2)
+ }
+ })
+ // Instead, there should be a change for its allowed portion (the X movement) with the Y
+ // staying frozen in the last valid resize position.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(
+ Rect(
+ newBounds2.left,
+ newBounds2.top,
+ newBounds2.right,
+ newBounds.bottom // Stayed at the first resize destination.
+ )
+ )
+ }
+ })
+ }
+
+ private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
+ return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
+ bounds == configuration.windowConfiguration.bounds
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() {
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
+ 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 DISALLOWED_RESIZE_AREA = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom)
+ private val STABLE_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 7e39b5b8f2ce..fc4bfd9754a0 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
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
@@ -32,6 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.Context;
@@ -41,14 +43,15 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
+import android.view.AttachedSurfaceControl;
import android.view.Display;
-import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager.LayoutParams;
-import android.window.TaskConstants;
+import android.window.SurfaceSyncGroup;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -96,9 +99,13 @@ public class WindowDecorationTests extends ShellTestCase {
@Mock
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
+ private AttachedSurfaceControl mMockRootSurfaceControl;
+ @Mock
private TestView mMockView;
@Mock
private WindowContainerTransaction mMockWindowContainerTransaction;
+ @Mock
+ private SurfaceSyncGroup mMockSurfaceSyncGroup;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -126,6 +133,8 @@ public class WindowDecorationTests extends ShellTestCase {
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
+ when(mMockSurfaceControlViewHost.getRootSurfaceControl())
+ .thenReturn(mMockRootSurfaceControl);
}
@Test
@@ -158,14 +167,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(false)
.build();
taskInfo.isFocused = false;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -192,10 +195,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
- createMockSurfaceControlBuilder(taskBackgroundSurface);
- mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder captionContainerSurfaceBuilder =
createMockSurfaceControlBuilder(captionContainerSurface);
@@ -212,14 +211,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -228,22 +221,10 @@ public class WindowDecorationTests extends ShellTestCase {
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
- verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
-
- verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
- verify(taskBackgroundSurfaceBuilder).setEffectLayer();
- verify(mMockSurfaceControlStartT).setWindowCrop(taskBackgroundSurface, 300, 100);
- verify(mMockSurfaceControlStartT)
- .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
- verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
- verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface,
- TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
- verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -256,21 +237,26 @@ public class WindowDecorationTests extends ShellTestCase {
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
- verify(mMockWindowContainerTransaction)
- .addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 364),
- new int[] { InsetsState.ITYPE_CAPTION_BAR });
+ verify(mMockWindowContainerTransaction).addInsetsSource(
+ eq(taskInfo.token),
+ any(),
+ eq(0 /* index */),
+ eq(WindowInsets.Type.captionBar()),
+ eq(new Rect(100, 300, 400, 364)));
}
verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ .setWindowCrop(taskSurface, 300, 100);
verify(mMockSurfaceControlStartT)
.show(taskSurface);
+ verify(mMockSurfaceControlStartT)
+ .setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
- assertEquals(380, mRelayoutResult.mWidth);
- assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(300, mRelayoutResult.mWidth);
+ assertEquals(100, mRelayoutResult.mHeight);
}
@Test
@@ -283,10 +269,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
- createMockSurfaceControlBuilder(taskBackgroundSurface);
- mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder captionContainerSurfaceBuilder =
createMockSurfaceControlBuilder(captionContainerSurface);
@@ -306,14 +288,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -323,7 +299,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlViewHost, never()).release();
verify(t, never()).apply();
verify(mMockWindowContainerTransaction, never())
- .removeInsetsProvider(eq(taskInfo.token), any());
+ .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo);
@@ -332,9 +308,9 @@ public class WindowDecorationTests extends ShellTestCase {
releaseOrder.verify(mMockSurfaceControlViewHost).release();
releaseOrder.verify(t).remove(captionContainerSurface);
releaseOrder.verify(t).remove(decorContainerSurface);
- releaseOrder.verify(t).remove(taskBackgroundSurface);
releaseOrder.verify(t).apply();
- verify(mMockWindowContainerTransaction).removeInsetsProvider(eq(taskInfo.token), any());
+ verify(mMockWindowContainerTransaction)
+ .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
}
@Test
@@ -392,10 +368,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
- createMockSurfaceControlBuilder(taskBackgroundSurface);
- mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder captionContainerSurfaceBuilder =
createMockSurfaceControlBuilder(captionContainerSurface);
@@ -415,11 +387,6 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
windowDecor.relayout(taskInfo);
@@ -434,7 +401,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
- verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
mContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
@@ -469,10 +436,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
- createMockSurfaceControlBuilder(taskBackgroundSurface);
- mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder captionContainerSurfaceBuilder =
createMockSurfaceControlBuilder(captionContainerSurface);
@@ -492,11 +455,6 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -504,12 +462,48 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
}
+ @Test
+ public void testRelayout_applyTransactionInSyncWithDraw() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
+
+ verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -565,6 +559,12 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */);
+ }
+
+ void relayout(ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw) {
+ mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
@@ -579,10 +579,8 @@ public class WindowDecorationTests extends ShellTestCase {
int cornerRadius = loadDimensionPixelSize(resources, mCaptionMenuCornerRadiusId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.desktop_mode_decor_handle_menu, name,
- mMockSurfaceControlAddWindowT,
- x - mRelayoutResult.mDecorContainerOffsetX,
- y - mRelayoutResult.mDecorContainerOffsetY,
+ addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
+ mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
width, height, shadowRadius, cornerRadius);
return additionalWindow;
}
diff --git a/libs/dream/lowlight/Android.bp b/libs/dream/lowlight/Android.bp
index 5b5b0f07cabd..e4d2e022cd76 100644
--- a/libs/dream/lowlight/Android.bp
+++ b/libs/dream/lowlight/Android.bp
@@ -25,6 +25,7 @@ filegroup {
name: "low_light_dream_lib-sources",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
],
path: "src",
}
@@ -37,10 +38,15 @@ android_library {
resource_dirs: [
"res",
],
+ libs: [
+ "kotlin-annotations",
+ ],
static_libs: [
"androidx.arch.core_core-runtime",
"dagger2",
"jsr330",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
diff --git a/libs/dream/lowlight/res/values/config.xml b/libs/dream/lowlight/res/values/config.xml
index 70fe0738a6f4..78fefbf41141 100644
--- a/libs/dream/lowlight/res/values/config.xml
+++ b/libs/dream/lowlight/res/values/config.xml
@@ -17,4 +17,7 @@
<resources>
<!-- The dream component used when the device is low light environment. -->
<string translatable="false" name="config_lowLightDreamComponent"/>
+ <!-- The max number of milliseconds to wait for the low light transition before setting
+ the system dream component -->
+ <integer name="config_lowLightTransitionTimeoutMs">2000</integer>
</resources>
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
deleted file mode 100644
index 5ecec4ddd1ad..000000000000
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ /dev/null
@@ -1,117 +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.dream.lowlight;
-
-import static com.android.dream.lowlight.dagger.LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT;
-
-import android.annotation.IntDef;
-import android.annotation.RequiresPermission;
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Maintains the ambient light mode of the environment the device is in, and sets a low light dream
- * component, if present, as the system dream when the ambient light mode is low light.
- *
- * @hide
- */
-public final class LowLightDreamManager {
- private static final String TAG = "LowLightDreamManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "AMBIENT_LIGHT_MODE_" }, value = {
- AMBIENT_LIGHT_MODE_UNKNOWN,
- AMBIENT_LIGHT_MODE_REGULAR,
- AMBIENT_LIGHT_MODE_LOW_LIGHT
- })
- public @interface AmbientLightMode {}
-
- /**
- * Constant for ambient light mode being unknown.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_UNKNOWN = 0;
-
- /**
- * Constant for ambient light mode being regular / bright.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_REGULAR = 1;
-
- /**
- * Constant for ambient light mode being low light / dim.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
-
- private final DreamManager mDreamManager;
-
- @Nullable
- private final ComponentName mLowLightDreamComponent;
-
- private int mAmbientLightMode = AMBIENT_LIGHT_MODE_UNKNOWN;
-
- @Inject
- public LowLightDreamManager(
- DreamManager dreamManager,
- @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
- mDreamManager = dreamManager;
- mLowLightDreamComponent = lowLightDreamComponent;
- }
-
- /**
- * Sets the current ambient light mode.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
- public void setAmbientLightMode(@AmbientLightMode int ambientLightMode) {
- if (mLowLightDreamComponent == null) {
- if (DEBUG) {
- Log.d(TAG, "ignore ambient light mode change because low light dream component "
- + "is empty");
- }
- return;
- }
-
- if (mAmbientLightMode == ambientLightMode) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "ambient light mode changed from " + mAmbientLightMode + " to "
- + ambientLightMode);
- }
-
- mAmbientLightMode = ambientLightMode;
-
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
- }
-}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
new file mode 100644
index 000000000000..96bfb78eff0d
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.Manifest
+import android.annotation.IntDef
+import android.annotation.RequiresPermission
+import android.app.DreamManager
+import android.content.ComponentName
+import android.util.Log
+import com.android.dream.lowlight.dagger.LowLightDreamModule
+import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+/**
+ * Maintains the ambient light mode of the environment the device is in, and sets a low light dream
+ * component, if present, as the system dream when the ambient light mode is low light.
+ *
+ * @hide
+ */
+class LowLightDreamManager @Inject constructor(
+ @Application private val coroutineScope: CoroutineScope,
+ private val dreamManager: DreamManager,
+ private val lowLightTransitionCoordinator: LowLightTransitionCoordinator,
+ @param:Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
+ private val lowLightDreamComponent: ComponentName?,
+ @param:Named(LowLightDreamModule.LOW_LIGHT_TRANSITION_TIMEOUT_MS)
+ private val lowLightTransitionTimeoutMs: Long
+) {
+ /**
+ * @hide
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ prefix = ["AMBIENT_LIGHT_MODE_"],
+ value = [
+ AMBIENT_LIGHT_MODE_UNKNOWN,
+ AMBIENT_LIGHT_MODE_REGULAR,
+ AMBIENT_LIGHT_MODE_LOW_LIGHT
+ ]
+ )
+ annotation class AmbientLightMode
+
+ private var mTransitionJob: Job? = null
+ private var mAmbientLightMode = AMBIENT_LIGHT_MODE_UNKNOWN
+ private val mLowLightTransitionTimeout =
+ lowLightTransitionTimeoutMs.toDuration(DurationUnit.MILLISECONDS)
+
+ /**
+ * Sets the current ambient light mode.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_DREAM_STATE)
+ fun setAmbientLightMode(@AmbientLightMode ambientLightMode: Int) {
+ if (lowLightDreamComponent == null) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "ignore ambient light mode change because low light dream component is empty"
+ )
+ }
+ return
+ }
+ if (mAmbientLightMode == ambientLightMode) {
+ return
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG, "ambient light mode changed from $mAmbientLightMode to $ambientLightMode"
+ )
+ }
+ mAmbientLightMode = ambientLightMode
+ val shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
+
+ // Cancel any previous transitions
+ mTransitionJob?.cancel()
+ mTransitionJob = coroutineScope.launch {
+ try {
+ lowLightTransitionCoordinator.waitForLowLightTransitionAnimation(
+ timeout = mLowLightTransitionTimeout,
+ entering = shouldEnterLowLight
+ )
+ } catch (ex: TimeoutCancellationException) {
+ Log.e(TAG, "timed out while waiting for low light animation", ex)
+ }
+ dreamManager.setSystemDreamComponent(
+ if (shouldEnterLowLight) lowLightDreamComponent else null
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "LowLightDreamManager"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+ /**
+ * Constant for ambient light mode being unknown.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_UNKNOWN = 0
+
+ /**
+ * Constant for ambient light mode being regular / bright.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_REGULAR = 1
+
+ /**
+ * Constant for ambient light mode being low light / dim.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_LOW_LIGHT = 2
+ }
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
new file mode 100644
index 000000000000..26efb55fa560
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import com.android.dream.lowlight.util.suspendCoroutineWithTimeout
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.coroutines.resume
+import kotlin.time.Duration
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+class LowLightTransitionCoordinator @Inject constructor() {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ fun onBeforeEnterLowLight(): Animator?
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ fun onBeforeExitLowLight(): Animator?
+ }
+
+ private var mLowLightEnterListener: LowLightEnterListener? = null
+ private var mLowLightExitListener: LowLightExitListener? = null
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ fun setLowLightEnterListener(lowLightEnterListener: LowLightEnterListener?) {
+ mLowLightEnterListener = lowLightEnterListener
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ fun setLowLightExitListener(lowLightExitListener: LowLightExitListener?) {
+ mLowLightExitListener = lowLightExitListener
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light, and waits for the
+ * animation to complete. If this function is cancelled, the animation is also cancelled.
+ *
+ * @param timeout the maximum duration to wait for the transition animation. If the animation
+ * does not complete within this time period, a
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ */
+ suspend fun waitForLowLightTransitionAnimation(timeout: Duration, entering: Boolean) =
+ suspendCoroutineWithTimeout(timeout) { continuation ->
+ var animator: Animator? = null
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener!!.onBeforeEnterLowLight()
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener!!.onBeforeExitLowLight()
+ }
+
+ if (animator == null) {
+ continuation.resume(Unit)
+ return@suspendCoroutineWithTimeout
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ val listener = object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ continuation.resume(Unit)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ continuation.cancel()
+ }
+ }
+ animator.addListener(listener)
+ continuation.invokeOnCancellation {
+ animator.removeListener(listener)
+ animator.cancel()
+ }
+ }
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java
deleted file mode 100644
index c183a04cb2f9..000000000000
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java
+++ /dev/null
@@ -1,61 +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.dream.lowlight.dagger;
-
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.dream.lowlight.R;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger module for low light dream.
- *
- * @hide
- */
-@Module
-public interface LowLightDreamModule {
- String LOW_LIGHT_DREAM_COMPONENT = "low_light_dream_component";
-
- /**
- * Provides dream manager.
- */
- @Provides
- static DreamManager providesDreamManager(Context context) {
- return context.getSystemService(DreamManager.class);
- }
-
- /**
- * Provides the component name of the low light dream, or null if not configured.
- */
- @Provides
- @Named(LOW_LIGHT_DREAM_COMPONENT)
- @Nullable
- static ComponentName providesLowLightDreamComponent(Context context) {
- final String lowLightDreamComponent = context.getResources().getString(
- R.string.config_lowLightDreamComponent);
- return lowLightDreamComponent.isEmpty() ? null
- : ComponentName.unflattenFromString(lowLightDreamComponent);
- }
-}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt
new file mode 100644
index 000000000000..dd274bd9d509
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight.dagger
+
+import android.app.DreamManager
+import android.content.ComponentName
+import android.content.Context
+import com.android.dream.lowlight.R
+import com.android.dream.lowlight.dagger.qualifiers.Application
+import com.android.dream.lowlight.dagger.qualifiers.Main
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Named
+
+/**
+ * Dagger module for low light dream.
+ *
+ * @hide
+ */
+@Module
+object LowLightDreamModule {
+ /**
+ * Provides dream manager.
+ */
+ @Provides
+ fun providesDreamManager(context: Context): DreamManager {
+ return requireNotNull(context.getSystemService(DreamManager::class.java))
+ }
+
+ /**
+ * Provides the component name of the low light dream, or null if not configured.
+ */
+ @Provides
+ @Named(LOW_LIGHT_DREAM_COMPONENT)
+ fun providesLowLightDreamComponent(context: Context): ComponentName? {
+ val lowLightDreamComponent = context.resources.getString(
+ R.string.config_lowLightDreamComponent
+ )
+ return if (lowLightDreamComponent.isEmpty()) {
+ null
+ } else {
+ ComponentName.unflattenFromString(lowLightDreamComponent)
+ }
+ }
+
+ @Provides
+ @Named(LOW_LIGHT_TRANSITION_TIMEOUT_MS)
+ fun providesLowLightTransitionTimeout(context: Context): Long {
+ return context.resources.getInteger(R.integer.config_lowLightTransitionTimeoutMs).toLong()
+ }
+
+ @Provides
+ @Main
+ fun providesMainDispatcher(): CoroutineDispatcher {
+ return Dispatchers.Main.immediate
+ }
+
+ @Provides
+ @Application
+ fun providesApplicationScope(@Main dispatcher: CoroutineDispatcher): CoroutineScope {
+ return CoroutineScope(dispatcher)
+ }
+
+ const val LOW_LIGHT_DREAM_COMPONENT = "low_light_dream_component"
+ const val LOW_LIGHT_TRANSITION_TIMEOUT_MS = "low_light_transition_timeout"
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt
new file mode 100644
index 000000000000..541fe4017e6e
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt
@@ -0,0 +1,9 @@
+package com.android.dream.lowlight.dagger.qualifiers
+
+import android.content.Context
+import javax.inject.Qualifier
+
+/**
+ * Used to qualify a context as [Context.getApplicationContext]
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Application
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt
new file mode 100644
index 000000000000..ccd0710bdc60
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt
@@ -0,0 +1,8 @@
+package com.android.dream.lowlight.dagger.qualifiers
+
+import javax.inject.Qualifier
+
+/**
+ * Used to qualify code running on the main thread.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Main
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt
new file mode 100644
index 000000000000..ff675ccfaffb
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight.util
+
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeout
+import kotlin.time.Duration
+
+suspend inline fun <T> suspendCoroutineWithTimeout(
+ timeout: Duration,
+ crossinline block: (CancellableContinuation<T>) -> Unit
+) = withTimeout(timeout) {
+ suspendCancellableCoroutine(block = block)
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index bd6f05eabac5..2d79090cd7d4 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -20,6 +20,7 @@ android_test {
name: "LowLightDreamTests",
srcs: [
"**/*.java",
+ "**/*.kt",
],
static_libs: [
"LowLightDreamLib",
@@ -28,6 +29,7 @@ android_test {
"androidx.test.ext.junit",
"frameworks-base-testutils",
"junit",
+ "kotlinx_coroutines_test",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"testables",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
deleted file mode 100644
index 91a170f7ae14..000000000000
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ /dev/null
@@ -1,98 +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.dream.lowlight;
-
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class LowLightDreamManagerTest {
- @Mock
- private DreamManager mDreamManager;
-
- @Mock
- private ComponentName mDreamComponent;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
- verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
- }
-
- @Test
- public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
-
- verify(mDreamManager).setSystemDreamComponent(null);
- }
-
- @Test
- public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- // Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- clearInvocations(mDreamManager);
-
- // Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
-
- verify(mDreamManager).setSystemDreamComponent(null);
- }
-
- @Test
- public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- null /*dream component*/);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
- verify(mDreamManager, never()).setSystemDreamComponent(any());
- }
-}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
new file mode 100644
index 000000000000..2a886bc31788
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.app.DreamManager
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import src.com.android.dream.lowlight.utils.any
+import src.com.android.dream.lowlight.utils.withArgCaptor
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LowLightDreamManagerTest {
+ @Mock
+ private lateinit var mDreamManager: DreamManager
+ @Mock
+ private lateinit var mEnterAnimator: Animator
+ @Mock
+ private lateinit var mExitAnimator: Animator
+
+ private lateinit var mTransitionCoordinator: LowLightTransitionCoordinator
+ private lateinit var mLowLightDreamManager: LowLightDreamManager
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope(StandardTestDispatcher())
+
+ mTransitionCoordinator = LowLightTransitionCoordinator()
+ mTransitionCoordinator.setLowLightEnterListener(
+ object : LowLightTransitionCoordinator.LowLightEnterListener {
+ override fun onBeforeEnterLowLight() = mEnterAnimator
+ })
+ mTransitionCoordinator.setLowLightExitListener(
+ object : LowLightTransitionCoordinator.LowLightExitListener {
+ override fun onBeforeExitLowLight() = mExitAnimator
+ })
+
+ mLowLightDreamManager = LowLightDreamManager(
+ coroutineScope = testScope,
+ dreamManager = mDreamManager,
+ lowLightTransitionCoordinator = mTransitionCoordinator,
+ lowLightDreamComponent = DREAM_COMPONENT,
+ lowLightTransitionTimeoutMs = LOW_LIGHT_TIMEOUT_MS
+ )
+ }
+
+ @Test
+ fun setAmbientLightMode_lowLight_setSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ verify(mDreamManager, never()).setSystemDreamComponent(DREAM_COMPONENT)
+ completeEnterAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ @Test
+ fun setAmbientLightMode_regularLight_clearSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
+ runCurrent()
+ verify(mDreamManager, never()).setSystemDreamComponent(null)
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_defaultUnknownMode_clearSystemDream() = testScope.runTest {
+ // Set to low light first.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ completeEnterAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ clearInvocations(mDreamManager)
+
+ // Return to default unknown mode.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN)
+ runCurrent()
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_dreamComponentNotSet_doNothing() = testScope.runTest {
+ val lowLightDreamManager = LowLightDreamManager(
+ coroutineScope = testScope,
+ dreamManager = mDreamManager,
+ lowLightTransitionCoordinator = mTransitionCoordinator,
+ lowLightDreamComponent = null,
+ lowLightTransitionTimeoutMs = LOW_LIGHT_TIMEOUT_MS
+ )
+ lowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ verify(mEnterAnimator, never()).addListener(any())
+ verify(mDreamManager, never()).setSystemDreamComponent(any())
+ }
+
+ @Test
+ fun setAmbientLightMode_multipleTimesBeforeAnimationEnds_cancelsPrevious() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ // If we reset the light mode back to regular before the previous animation finishes, it
+ // should be ignored.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
+ runCurrent()
+ completeEnterAnimations()
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager, times(1)).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_animatorNeverFinishes_timesOut() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ advanceTimeBy(delayTimeMillis = LOW_LIGHT_TIMEOUT_MS + 1)
+ // Animation never finishes, but we should still set the system dream
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ private fun completeEnterAnimations() {
+ val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+ listener.onAnimationEnd(mEnterAnimator)
+ }
+
+ private fun completeExitAnimations() {
+ val listener = withArgCaptor { verify(mExitAnimator).addListener(capture()) }
+ listener.onAnimationEnd(mExitAnimator)
+ }
+
+ companion object {
+ private val DREAM_COMPONENT = ComponentName("test_package", "test_dream")
+ private const val LOW_LIGHT_TIMEOUT_MS: Long = 1000
+ }
+} \ No newline at end of file
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
new file mode 100644
index 000000000000..4c526a6ac69d
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dream.lowlight.LowLightTransitionCoordinator.LowLightEnterListener
+import com.android.dream.lowlight.LowLightTransitionCoordinator.LowLightExitListener
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import src.com.android.dream.lowlight.utils.whenever
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LowLightTransitionCoordinatorTest {
+ @Mock
+ private lateinit var mEnterListener: LowLightEnterListener
+
+ @Mock
+ private lateinit var mExitListener: LowLightExitListener
+
+ @Mock
+ private lateinit var mAnimator: Animator
+
+ @Captor
+ private lateinit var mAnimatorListenerCaptor: ArgumentCaptor<Animator.AnimatorListener>
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope(StandardTestDispatcher())
+ }
+
+ @Test
+ fun onEnterCalledOnListeners() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ verify(mEnterListener).onBeforeEnterLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun onExitCalledOnListeners() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightExitListener(mExitListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = false)
+ }
+ runCurrent()
+ verify(mExitListener).onBeforeExitLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun listenerNotCalledAfterRemoval() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ coordinator.setLowLightEnterListener(null)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ verify(mEnterListener, never()).onBeforeEnterLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun waitsForAnimationToEnd() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ assertThat(job.isCompleted).isFalse()
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.value.onAnimationEnd(mAnimator)
+ runCurrent()
+ assertThat(job.isCompleted).isTrue()
+ assertThat(job.isCancelled).isFalse()
+ }
+
+ @Test
+ fun waitsForTimeoutIfAnimationNeverEnds() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ assertThat(job.isCancelled).isFalse()
+ advanceTimeBy(delayTimeMillis = TIMEOUT.inWholeMilliseconds + 1)
+ // If animator doesn't complete within the timeout, we should cancel ourselves.
+ assertThat(job.isCancelled).isTrue()
+ }
+
+ @Test
+ fun shouldCancelIfAnimationIsCancelled() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ assertThat(job.isCompleted).isFalse()
+ assertThat(job.isCancelled).isFalse()
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.value.onAnimationCancel(mAnimator)
+ runCurrent()
+ assertThat(job.isCompleted).isTrue()
+ assertThat(job.isCancelled).isTrue()
+ }
+
+ @Test
+ fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ verify(mAnimator, never()).cancel()
+ assertThat(job.isCompleted).isFalse()
+
+ job.cancel()
+ // We should have removed the listener and cancelled the animator
+ verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
+ verify(mAnimator).cancel()
+ }
+
+ companion object {
+ private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt
new file mode 100644
index 000000000000..e5ec26ca4b41
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt
@@ -0,0 +1,125 @@
+package src.com.android.dream.lowlight.utils
+
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.stubbing.OngoingStubbing
+import org.mockito.stubbing.Stubber
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+inline fun <reified T> any(): T = any(T::class.java)
+
+/**
+ * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher)
+
+/**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ *
+ * @param apply builder function to simplify stub configuration by improving type inference.
+ */
+inline fun <reified T : Any> mock(apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java)
+ .apply(apply)
+
+/**
+ * Helper function for stubbing methods without the need to use backticks.
+ *
+ * @see Mockito.when
+ */
+fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+fun <T> Stubber.whenever(mock: T): T = `when`(mock)
+
+/**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ * java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+ private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+ fun capture(): T = wrapped.capture()
+ val value: T
+ get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+ KotlinArgumentCaptor(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured = captor.value
+ *
+ * becomes:
+ *
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
+ */
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+ kotlinArgumentCaptor<T>().apply { block() }.value
+
+/**
+ * Variant of [withArgCaptor] for capturing multiple arguments.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured: List<Foo> = captor.allValues
+ *
+ * becomes:
+ *
+ * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) }
+ */
+inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> =
+ kotlinArgumentCaptor<T>().apply{ block() }.allValues
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7228b895ebc4..34af1f9ea039 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -55,6 +55,10 @@ cc_defaults {
// GCC false-positives on this warning, and since we -Werror that's
// a problem
"-Wno-free-nonheap-object",
+
+ // Do not de-optimise cold code paths in AFDO.
+ // Some code paths might be infrequently executed but critical to latency.
+ "-fno-profile-sample-accurate",
],
include_dirs: [
@@ -395,7 +399,7 @@ cc_defaults {
"libharfbuzz_ng",
"libimage_io",
"libjpeg",
- "libjpegrecoverymap",
+ "libultrahdr",
"liblog",
"libminikin",
"libz",
@@ -537,6 +541,7 @@ cc_defaults {
"AnimatorManager.cpp",
"CanvasTransform.cpp",
"DamageAccumulator.cpp",
+ "Gainmap.cpp",
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
@@ -737,6 +742,9 @@ cc_test {
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
+ data: [
+ ":hwuimicro",
+ ],
}
// ------------------------
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
new file mode 100644
index 000000000000..30f401ef5f01
--- /dev/null
+++ b/libs/hwui/Gainmap.cpp
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Gainmap.h"
+
+namespace android::uirenderer {
+
+sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
+ auto gainmap = sp<Gainmap>::make();
+ gainmap->info = srcGainmap->info;
+ const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
+ if (!skBitmap.get()) {
+ return nullptr;
+ }
+ gainmap->bitmap = std::move(skBitmap);
+ return gainmap;
+}
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/Gainmap.h b/libs/hwui/Gainmap.h
index 765f98a257b8..3bc183ab854f 100644
--- a/libs/hwui/Gainmap.h
+++ b/libs/hwui/Gainmap.h
@@ -27,6 +27,7 @@ class Gainmap : public LightRefBase<Gainmap> {
public:
SkGainmapInfo info;
sk_sp<Bitmap> bitmap;
+ static sp<Gainmap> allocateHardwareGainmap(const sp<Gainmap>& srcGainmap);
};
} // namespace android::uirenderer
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 41ced8cebf83..139cdde5c0d2 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -54,6 +54,7 @@ struct MemoryPolicy {
// collection
bool purgeScratchOnly = true;
// EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+ // WARNING: Enabling this option can lead to instability, see b/266626090
bool releaseContextOnStoppedOnly = false;
};
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b0896daee2a1..7af6efb7da41 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -91,6 +91,8 @@ bool Properties::isHighEndGfx = true;
bool Properties::isLowRam = false;
bool Properties::isSystemOrPersistent = false;
+float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -102,7 +104,7 @@ bool Properties::load() {
debugOverdraw = false;
std::string debugOverdrawProperty = base::GetProperty(PROPERTY_DEBUG_OVERDRAW, "");
if (debugOverdrawProperty != "") {
- INIT_LOGD(" Overdraw debug enabled: %s", debugOverdrawProperty);
+ INIT_LOGD(" Overdraw debug enabled: %s", debugOverdrawProperty.c_str());
if (debugOverdrawProperty == "show") {
debugOverdraw = true;
overdrawColorSet = OverdrawColorSet::Default;
@@ -150,6 +152,11 @@ bool Properties::load() {
enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
+ auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str());
+ if (hdrHeadroom >= 1.f) {
+ maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f);
+ }
+
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ed7175e140e4..24e206bbc3b1 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -218,6 +218,8 @@ enum DebugLevel {
#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -321,6 +323,8 @@ public:
static bool isLowRam;
static bool isSystemOrPersistent;
+ static float maxHdrHeadroomOn8bit;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 0b58406516e3..924fbd659824 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -51,6 +51,9 @@
#include "include/gpu/GrDirectContext.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
+#ifdef __ANDROID__
+#include "renderthread/CanvasContext.h"
+#endif
namespace android {
namespace uirenderer {
@@ -489,7 +492,19 @@ struct DrawPoints final : Op {
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ if (paint.isAntiAlias()) {
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ } else {
+ c->save();
+#ifdef __ANDROID__
+ auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix();
+ auto transform = c->getLocalToDevice();
+ transform.postConcat(pixelSnap);
+ c->setMatrix(transform);
+#endif
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ c->restore();
+ }
}
};
struct DrawVertices final : Op {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7a1276982d0a..8394c3cd4175 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -50,6 +50,7 @@
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
+#include "effects/GainmapRenderer.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include "hwui/PaintFilter.h"
@@ -589,18 +590,25 @@ void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Pain
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto image = bitmap.makeImage();
+
+ if (bitmap.hasGainmap()) {
+ Paint gainmapPaint = paint ? *paint : Paint();
+ sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ SkTileMode::kClamp, SkTileMode::kClamp, gainmapPaint.sampling());
+ gainmapPaint.setShader(gainmapShader);
+ return drawRect(left, top, left + bitmap.width(), top + bitmap.height(), gainmapPaint);
+ }
+
applyLooper(paint, [&](const Paint& p) {
mCanvas->drawImage(image, left, top, p.sampling(), &p);
});
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) {
- auto image = bitmap.makeImage();
SkAutoCanvasRestore acr(mCanvas, true);
mCanvas->concat(matrix);
- applyLooper(paint, [&](const Paint& p) {
- mCanvas->drawImage(image, 0, 0, p.sampling(), &p);
- });
+ drawBitmap(bitmap, 0, 0, paint);
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -610,6 +618,16 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float s
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ if (bitmap.hasGainmap()) {
+ Paint gainmapPaint = paint ? *paint : Paint();
+ sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ SkTileMode::kClamp, SkTileMode::kClamp, gainmapPaint.sampling());
+ gainmapShader = gainmapShader->makeWithLocalMatrix(SkMatrix::RectToRect(srcRect, dstRect));
+ gainmapPaint.setShader(gainmapShader);
+ return drawRect(dstLeft, dstTop, dstRight, dstBottom, gainmapPaint);
+ }
+
applyLooper(paint, [&](const Paint& p) {
mCanvas->drawImageRect(image, srcRect, dstRect, p.sampling(), &p,
SkCanvas::kFast_SrcRectConstraint);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 8977d3ce4da3..bfe4eaf39e21 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -23,21 +23,55 @@
#include "utils/Trace.h"
#ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
#include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
#endif
namespace android::uirenderer {
using namespace renderthread;
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+ // We should always have a known destination colorspace. If we don't we must be in some
+ // legacy mode where we're lost and also definitely not going to HDR
+ if (destColorspace == nullptr) {
+ return 1.f;
+ }
+
+ constexpr float GenericSdrWhiteNits = 203.f;
+ constexpr float maxPQLux = 10000.f;
+ constexpr float maxHLGLux = 1000.f;
+ skcms_TransferFunction destTF;
+ destColorspace->transferFn(&destTF);
+ if (skcms_TransferFunction_isPQish(&destTF)) {
+ return maxPQLux / GenericSdrWhiteNits;
+ } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+ return maxHLGLux / GenericSdrWhiteNits;
+ } else {
+#ifdef __ANDROID__
+ CanvasContext* context = CanvasContext::getActiveContext();
+ return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+ return 1.f;
+#endif
+ }
+}
+
void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
ATRACE_CALL();
#ifdef __ANDROID__
- CanvasContext* context = CanvasContext::getActiveContext();
- float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
+ auto destColorspace = c->imageInfo().refColorSpace();
+ float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
if (targetSdrHdrRatio > 1.f && gainmapImage) {
SkPaint gainmapPaint = *paint;
float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
gainmapSrc.fRight *= sX;
gainmapSrc.fTop *= sY;
gainmapSrc.fBottom *= sY;
- auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
- sampling, gainmapInfo, dst, targetSdrHdrRatio,
- c->imageInfo().refColorSpace());
+ auto shader =
+ SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+ gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
gainmapPaint.setShader(shader);
c->drawRect(dst, gainmapPaint);
} else
@@ -58,4 +92,213 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
}
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+ uniform shader base;
+ uniform shader gainmap;
+ uniform colorFilter workingSpaceToLinearSrgb;
+ uniform half4 logRatioMin;
+ uniform half4 logRatioMax;
+ uniform half4 gainmapGamma;
+ uniform half4 epsilonSdr;
+ uniform half4 epsilonHdr;
+ uniform half W;
+ uniform int gainmapIsAlpha;
+ uniform int gainmapIsRed;
+ uniform int singleChannel;
+ uniform int noGamma;
+
+ half4 toDest(half4 working) {
+ half4 ls = workingSpaceToLinearSrgb.eval(working);
+ vec3 dest = fromLinearSrgb(ls.rgb);
+ return half4(dest.r, dest.g, dest.b, ls.a);
+ }
+
+ half4 main(float2 coord) {
+ half4 S = base.eval(coord);
+ half4 G = gainmap.eval(coord);
+ if (gainmapIsAlpha == 1) {
+ G = half4(G.a, G.a, G.a, 1.0);
+ }
+ if (gainmapIsRed == 1) {
+ G = half4(G.r, G.r, G.r, 1.0);
+ }
+ if (singleChannel == 1) {
+ half L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.r, logRatioMax.r, G.r);
+ } else {
+ L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ } else {
+ half3 L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+ } else {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ }
+ }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+ static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+ auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+ if (buildResult.effect) {
+ return buildResult.effect.release();
+ } else {
+ LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+ }
+ }();
+ SkASSERT(effect);
+ return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+ return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+ sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+ SkRuntimeShaderBuilder mBuilder{mShader};
+ SkGainmapInfo mGainmapInfo;
+ std::mutex mUniformGuard;
+
+ void setupChildren(const sk_sp<const SkImage>& baseImage,
+ const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+ sk_sp<SkColorSpace> baseColorSpace =
+ baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+ // Determine the color space in which the gainmap math is to be applied.
+ sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+ // Create a color filter to transform from the base image's color space to the color space
+ // in which the gainmap is to be applied.
+ auto colorXformSdrToGainmap =
+ SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+ // The base image shader will convert into the color space in which the gainmap is applied.
+ auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+ ->makeWithColorFilter(colorXformSdrToGainmap);
+
+ // The gainmap image shader will ignore any color space that the gainmap has.
+ const SkMatrix gainmapRectToDstRect =
+ SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+ SkRect::MakeWH(baseImage->width(), baseImage->height()));
+ auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+ &gainmapRectToDstRect);
+
+ // Create a color filter to transform from the color space in which the gainmap is applied
+ // to the intermediate destination color space.
+ auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+ gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+ mBuilder.child("base") = std::move(baseImageShader);
+ mBuilder.child("gainmap") = std::move(gainmapImageShader);
+ mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+ }
+
+ void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo) {
+ const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+ const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+ const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+ gainmapInfo.fGainmapGamma.fG == 1.f &&
+ gainmapInfo.fGainmapGamma.fB == 1.f;
+ const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+ const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+ const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+ const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+ (colorTypeFlags == kGray_SkColorChannelFlag ||
+ colorTypeFlags == kAlpha_SkColorChannelFlag ||
+ colorTypeFlags == kRed_SkColorChannelFlag);
+ mBuilder.uniform("logRatioMin") = logRatioMin;
+ mBuilder.uniform("logRatioMax") = logRatioMax;
+ mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+ mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+ mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+ mBuilder.uniform("noGamma") = noGamma;
+ mBuilder.uniform("singleChannel") = singleChannel;
+ mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+ mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+ }
+
+ sk_sp<const SkData> build(float targetHdrSdrRatio) {
+ sk_sp<const SkData> uniforms;
+ {
+ // If we are called concurrently from multiple threads, we need to guard the call
+ // to writableUniforms() which mutates mUniform. This is otherwise safe because
+ // writeableUniforms() will make a copy if it's not unique before mutating
+ // This can happen if a BitmapShader is used on multiple canvas', such as a
+ // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+ std::lock_guard _lock(mUniformGuard);
+ const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ mBuilder.uniform("W") = W;
+ uniforms = mBuilder.uniforms();
+ }
+ return uniforms;
+ }
+
+public:
+ explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ mGainmapInfo = gainmapInfo;
+ setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+ setupGenericUniforms(gainmapImage, gainmapInfo);
+ }
+
+ static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+ image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+ auto callback =
+ [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+ -> sk_sp<const SkData> {
+ return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+ };
+ return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+ deferredHandler->mBuilder.children());
+ }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+ sampling);
+}
+
+#else // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return nullptr;
+}
+
+#endif // __ANDROID__
+
} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 7c56d94d9776..4ed2445da17e 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -30,4 +30,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index d08bc5c583c2..8049dc946c9e 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -29,9 +29,10 @@
namespace android {
-AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
- : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
- mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+ SkEncodedImageFormat format)
+ : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) {
+ mTimeToShowNextSnapshot = ms2ns(currentFrameDuration());
setStagingBounds(mSkAnimatedImage->getBounds());
}
@@ -92,7 +93,7 @@ bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) {
// directly from mSkAnimatedImage.
lock.unlock();
std::unique_lock imageLock{mImageLock};
- *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration());
+ *outDelay = ms2ns(currentFrameDuration());
return true;
} else {
// The next snapshot has not yet been decoded, but we've already passed
@@ -109,7 +110,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
Snapshot snap;
{
std::unique_lock lock{mImageLock};
- snap.mDurationMS = mSkAnimatedImage->decodeNextFrame();
+ snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
}
@@ -123,7 +124,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
- snap.mDurationMS = mSkAnimatedImage->currentFrameDuration();
+ snap.mDurationMS = currentFrameDuration();
}
return snap;
@@ -274,7 +275,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- durationMS = mSkAnimatedImage->currentFrameDuration();
+ durationMS = currentFrameDuration();
}
{
std::unique_lock lock{mSwapLock};
@@ -306,7 +307,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
{
std::unique_lock lock{mImageLock};
if (update) {
- durationMS = mSkAnimatedImage->decodeNextFrame();
+ durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
}
canvas->drawDrawable(mSkAnimatedImage.get());
@@ -336,4 +337,20 @@ SkRect AnimatedImageDrawable::onGetBounds() {
return SkRectMakeLargest();
}
+int AnimatedImageDrawable::adjustFrameDuration(int durationMs) {
+ if (durationMs == SkAnimatedImage::kFinished) {
+ return SkAnimatedImage::kFinished;
+ }
+
+ if (mFormat == SkEncodedImageFormat::kGIF) {
+ // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms
+ return durationMs <= 10 ? 100 : durationMs;
+ }
+ return durationMs;
+}
+
+int AnimatedImageDrawable::currentFrameDuration() {
+ return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 8ca3c7e125f1..1e965abc82b5 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -16,16 +16,16 @@
#pragma once
-#include <cutils/compiler.h>
-#include <utils/Macros.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
-
#include <SkAnimatedImage.h>
#include <SkCanvas.h>
#include <SkColorFilter.h>
#include <SkDrawable.h>
+#include <SkEncodedImageFormat.h>
#include <SkPicture.h>
+#include <cutils/compiler.h>
+#include <utils/Macros.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
#include <future>
#include <mutex>
@@ -48,7 +48,8 @@ class AnimatedImageDrawable : public SkDrawable {
public:
// bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the
// Snapshots.
- AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed);
+ AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+ SkEncodedImageFormat format);
/**
* This updates the internal time and returns true if the image needs
@@ -115,6 +116,7 @@ protected:
private:
sk_sp<SkAnimatedImage> mSkAnimatedImage;
const size_t mBytesUsed;
+ const SkEncodedImageFormat mFormat;
bool mRunning = false;
bool mStarting = false;
@@ -157,6 +159,9 @@ private:
Properties mProperties;
std::unique_ptr<OnAnimationEndListener> mEndListener;
+
+ int adjustFrameDuration(int);
+ int currentFrameDuration();
};
} // namespace android
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 8266beb4ee8c..9a06be006dca 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -498,7 +498,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
return result;
}
-SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
std::unique_ptr<SkStream> gainmapStream;
@@ -553,9 +553,12 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
return SkCodec::kInternalError;
}
- // TODO: We don't currently parcel the gainmap, but if we should then also support
- // the shared allocator
- sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ sk_sp<Bitmap> nativeBitmap;
+ if (isShared) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
bitmapInfo.height());
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 97573e1e8207..b3781b52a418 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -79,7 +79,7 @@ public:
// Set whether the ImageDecoder should handle RestorePrevious frames.
void setHandleRestorePrevious(bool handle);
- SkCodec::Result extractGainmap(Bitmap* destination);
+ SkCodec::Result extractGainmap(Bitmap* destination, bool isShared);
private:
// State machine for keeping track of how to handle RestorePrevious (RP)
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 373e893b9a25..a7f5aa83e624 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -97,7 +97,7 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
bytesUsed += picture->approximateBytesUsed();
}
-
+ SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat();
sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
info, subset,
std::move(picture));
@@ -108,8 +108,8 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
bytesUsed += sizeof(animatedImg.get());
- sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg),
- bytesUsed));
+ sk_sp<AnimatedImageDrawable> drawable(
+ new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format));
return reinterpret_cast<jlong>(drawable.release());
}
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 3f9c4bd2aeff..6ee7576651f2 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -386,15 +386,10 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfig
return NULL;
}
if (hasGainmap) {
- auto gainmap = sp<uirenderer::Gainmap>::make();
- gainmap->info = original.gainmap()->info;
- const SkBitmap skSrcBitmap = original.gainmap()->bitmap->getSkBitmap();
- sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
- if (!skBitmap.get()) {
- return NULL;
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(original.gainmap());
+ if (gm) {
+ bitmap->setGainmap(std::move(gm));
}
- gainmap->bitmap = std::move(skBitmap);
- bitmap->setGainmap(std::move(gainmap));
}
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 571ab833f053..c57e6f09347a 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -637,7 +637,10 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
return nullObjectReturn("Failed to allocate a hardware bitmap");
}
if (hasGainmap) {
- hardwareBitmap->setGainmap(std::move(gainmap));
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index f93be038cc36..aeaa17198be5 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -334,7 +334,10 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
if (isHardware) {
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
if (hasGainmap) {
- hardwareBitmap->setGainmap(std::move(gainmap));
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 28d78b4474f7..914266de2753 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -579,17 +579,9 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace,
LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
jobject params;
- if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
- params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g, true);
- } else {
- params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g, false);
- }
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c, transferParams.d,
+ transferParams.e, transferParams.f, transferParams.g);
jfloatArray xyzArray = env->NewFloatArray(9);
jfloat xyz[9] = {
@@ -817,7 +809,7 @@ int register_android_graphics_Graphics(JNIEnv* env)
gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/ColorSpace$Rgb$TransferParameters"));
gTransferParameters_constructorMethodID =
- GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
+ GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDD)V");
gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index fda7080f2864..db1c188e425e 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -354,7 +354,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
// cost of RAM
if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
// The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
- result = decoder->extractGainmap(nativeBitmap.get());
+ result = decoder->extractGainmap(nativeBitmap.get(),
+ allocator == kSharedMemory_Allocator ? true : false);
jexception = get_and_clear_exception(env);
}
@@ -469,8 +470,10 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
if (hwBitmap) {
hwBitmap->setImmutable();
if (nativeBitmap->hasGainmap()) {
- // TODO: Also convert to a HW gainmap image
- hwBitmap->setGainmap(nativeBitmap->gainmap());
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(nativeBitmap->gainmap());
+ if (gm) {
+ hwBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 75d45e5bd8aa..7eb79be6f55b 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,9 @@
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"
+#include <vector>
+
+#include "Gainmap.h"
#include "GraphicsJNI.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
@@ -17,10 +20,9 @@
#include "SkShader.h"
#include "SkString.h"
#include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
#include "include/effects/SkRuntimeEffect.h"
-#include <vector>
-
using namespace android::uirenderer;
/**
@@ -74,7 +76,20 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
- image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+ auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+ image = bitmap.makeImage();
+
+ if (!isDirectSampled && bitmap.hasGainmap()) {
+ sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (gainmapShader) {
+ if (matrix) {
+ gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+ }
+ return reinterpret_cast<jlong>(gainmapShader.release());
+ }
+ }
}
if (!image.get()) {
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 8874ef1d2fe0..69418b09fee6 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -298,39 +298,39 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
}
///////////////////////////////////////////////////////////////////////////////
-using namespace android::jpegrecoverymap;
+using namespace android::ultrahdr;
-jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
+ultrahdr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
switch (aDataSpace & ADataSpace::STANDARD_MASK) {
case ADataSpace::STANDARD_BT709:
- return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+ return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
case ADataSpace::STANDARD_DCI_P3:
- return jpegr_color_gamut::JPEGR_COLORGAMUT_P3;
+ return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_P3;
case ADataSpace::STANDARD_BT2020:
- return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+ return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
default:
jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(IllegalArgumentException,
"The requested color gamut is not supported by JPEG/R.");
}
- return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED;
+ return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
}
-jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
+ultrahdr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
int aDataSpace) {
switch (aDataSpace & ADataSpace::TRANSFER_MASK) {
case ADataSpace::TRANSFER_ST2084:
- return jpegr_transfer_function::JPEGR_TF_PQ;
+ return ultrahdr_transfer_function::ULTRAHDR_TF_PQ;
case ADataSpace::TRANSFER_HLG:
- return jpegr_transfer_function::JPEGR_TF_HLG;
+ return ultrahdr_transfer_function::ULTRAHDR_TF_HLG;
default:
jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(IllegalArgumentException,
"The requested HDR transfer function is not supported by JPEG/R.");
}
- return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED;
+ return ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED;
}
bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
@@ -344,13 +344,13 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
return false;
}
- jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
- jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
- jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
+ ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
+ ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
+ ultrahdr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
- if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
- || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
- || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) {
+ if (hdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED
+ || sdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED
+ || hdrTransferFunction == ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED) {
return false;
}
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index d22a26c83567..8ef780547184 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,7 +2,7 @@
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#include <android/data_space.h>
-#include <jpegrecoverymap/jpegr.h>
+#include <ultrahdr/jpegr.h>
extern "C" {
#include "jpeglib.h"
@@ -103,7 +103,7 @@ public:
* @param aDataSpace data space defined in data_space.h.
* @return color gamut for JPEG/R.
*/
- static android::jpegrecoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
+ static android::ultrahdr::ultrahdr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
/** Map data space (defined in DataSpace.java and data_space.h) to the transfer function
* used in JPEG/R
@@ -112,7 +112,7 @@ public:
* @param aDataSpace data space defined in data_space.h.
* @return color gamut for JPEG/R.
*/
- static android::jpegrecoverymap::jpegr_transfer_function findHdrTransferFunction(
+ static android::ultrahdr::ultrahdr_transfer_function findHdrTransferFunction(
JNIEnv* env, int aDataSpace);
};
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index ae22213f4bf4..706f18c3be80 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -74,7 +74,7 @@ static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject,
auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer);
auto* rootRenderNode = reinterpret_cast<RootRenderNode*>(renderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
- auto* proxy = new RenderProxy(true, rootRenderNode, &factory);
+ auto* proxy = new RenderProxy(false, rootRenderNode, &factory);
proxy->setHardwareBuffer(hardwareBuffer);
return (jlong)proxy;
}
@@ -85,28 +85,20 @@ static void HardwareBufferRenderer_destroy(jlong renderProxy) {
}
static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
- auto matrix = SkMatrix();
switch (transform) {
case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
- matrix.setRotate(90);
- matrix.postTranslate(width, 0);
- break;
+ return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
- matrix.setRotate(180);
- matrix.postTranslate(width, height);
- break;
+ return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
- matrix.setRotate(270);
- matrix.postTranslate(0, width);
- break;
+ return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
default:
ALOGE("Invalid transform provided. Transform should be validated from"
"the java side. Leveraging identity transform as a fallback");
[[fallthrough]];
case ANATIVEWINDOW_TRANSFORM_IDENTITY:
- break;
+ return SkMatrix::I();
}
- return matrix;
}
static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy,
@@ -117,8 +109,8 @@ static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject,
auto skHeight = static_cast<SkScalar>(height);
auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
- proxy->setHardwareBufferRenderParams(
- HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+ proxy->setHardwareBufferRenderParams(HardwareBufferRenderParams(
+ width, height, matrix, colorSpace, createRenderCallback(env, consumer)));
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
UiFrameInfoBuilder(proxy->frameInfo())
.setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index db7639029187..ac1f92dee507 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -605,15 +605,25 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
}
mPreviousPosition = bounds;
+ ATRACE_NAME("Update SurfaceView position");
+
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
- incStrong(0);
- auto functor = std::bind(
- std::mem_fn(&PositionListenerTrampoline::doUpdatePositionAsync), this,
- (jlong) info.canvasContext.getFrameNumber(),
- (jint) bounds.left, (jint) bounds.top,
- (jint) bounds.right, (jint) bounds.bottom);
-
- info.canvasContext.enqueueFrameWork(std::move(functor));
+ JNIEnv* env = jnienv();
+ // Update the new position synchronously. We cannot defer this to
+ // a worker pool to process asynchronously because the UI thread
+ // may be unblocked by the time a worker thread can process this,
+ // In particular if the app removes a view from the view tree before
+ // this callback is dispatched, then we lose the position
+ // information for this frame.
+ jboolean keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
+ static_cast<jlong>(info.canvasContext.getFrameNumber()),
+ static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+ static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+ if (!keepListening) {
+ env->DeleteGlobalRef(mListener);
+ mListener = nullptr;
+ }
#endif
}
@@ -628,7 +638,14 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
ATRACE_NAME("SurfaceView position lost");
JNIEnv* env = jnienv();
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
- // TODO: Remember why this is synchronous and then make a comment
+ // Update the lost position synchronously. We cannot defer this to
+ // a worker pool to process asynchronously because the UI thread
+ // may be unblocked by the time a worker thread can process this,
+ // In particular if a view's rendernode is readded to the scene
+ // before this callback is dispatched, then we report that we lost
+ // position information on the wrong frame, which can be problematic
+ // for views like SurfaceView which rely on RenderNode callbacks
+ // for driving visibility.
jboolean keepListening = env->CallStaticBooleanMethod(
gPositionListener.clazz, gPositionListener.callPositionLost, mListener,
info ? info->canvasContext.getFrameNumber() : 0);
@@ -708,23 +725,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
}
}
- void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
- jint right, jint bottom) {
- ATRACE_NAME("Update SurfaceView position");
-
- JNIEnv* env = jnienv();
- jboolean keepListening = env->CallStaticBooleanMethod(
- gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
- frameNumber, left, top, right, bottom);
- if (!keepListening) {
- env->DeleteGlobalRef(mListener);
- mListener = nullptr;
- }
-
- // We need to release ourselves here
- decStrong(0);
- }
-
JavaVM* mVm;
jobject mListener;
uirenderer::Rect mPreviousPosition;
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f17129c8d953..1af60b2f5fae 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -108,8 +108,9 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo
std::move(data), std::string_view(fontPath.c_str(), fontPath.size()),
fontPtr, fontSize, ttcIndex, builder->axes);
if (minikinFont == nullptr) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "Failed to create internal object. maybe invalid font data.");
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Failed to create internal object. maybe invalid font data. filePath %s",
+ fontPath.c_str());
return 0;
}
uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 1a47db5c8ec2..da4f66d45a70 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -293,8 +293,10 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
// with the same canvas transformation + clip into the target
// canvas then draw the layer on top
if (renderNode->hasHolePunches()) {
+ canvas->save();
TransformCanvas transformCanvas(canvas, SkBlendMode::kDstOut);
displayList->draw(&transformCanvas);
+ canvas->restore();
}
canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
SkRect::Make(dstBounds), sampling, &paint,
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 202a62cf320c..c4d3f5cedfa8 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+ bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
if (mHardwareBuffer) {
@@ -65,19 +67,44 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
+
+ // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+ // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+ // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+ // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+ // The discussion was not resolved with a clear consensus
+ if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+ GLint curReadFB = 0;
+ GLint curDrawFB = 0;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+ GLint buffer = GL_NONE;
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+ if (buffer == GL_NONE) {
+ const GLenum drawBuffer = GL_BACK;
+ glDrawBuffers(1, &drawBuffer);
+ }
+
+ glGetIntegerv(GL_READ_BUFFER, &buffer);
+ if (buffer == GL_NONE) {
+ glReadBuffer(GL_BACK);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+ GL_CHECKPOINT(LOW);
+ }
+
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
Frame SkiaOpenGLPipeline::getFrame() {
- if (mHardwareBuffer) {
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(mHardwareBuffer, &description);
- return Frame(description.width, description.height, 0);
- } else {
- LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
- "drawRenderNode called on a context with no surface!");
- return mEglManager.beginFrame(mEglSurface);
- }
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no surface!");
+ return mEglManager.beginFrame(mEglSurface);
}
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
@@ -110,7 +137,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
+ kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 940d6bfdb83c..f0461bef170c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -53,6 +53,14 @@ public:
bool isSurfaceReady() override;
bool isContextReady() override;
+ const SkM44& getPixelSnapMatrix() const override {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset);
+ return sSnapMatrix;
+ }
+
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
protected:
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6628463bcabf..b020e966e05a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -36,15 +36,15 @@
#include <SkStream.h>
#include <SkString.h>
#include <SkTypeface.h>
-#include "include/gpu/GpuTypes.h" // from Skia
#include <android-base/properties.h>
+#include <gui/TraceUtils.h>
#include <unistd.h>
#include <sstream>
-#include <gui/TraceUtils.h>
#include "LightingInfo.h"
#include "VectorDrawable.h"
+#include "include/gpu/GpuTypes.h" // from Skia
#include "thread/CommonPool.h"
#include "tools/SkSharingProc.h"
#include "utils/Color.h"
@@ -499,8 +499,7 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip,
}
canvas->concat(preTransform);
- // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
- if (!opaque || getSurfaceColorType() == kRGBA_F16_SkColorType) {
+ if (!opaque) {
canvas->clear(SK_ColorTRANSPARENT);
}
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 99298bc0fe9b..86096d5bd01c 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -66,15 +66,8 @@ MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
}
Frame SkiaVulkanPipeline::getFrame() {
- if (mHardwareBuffer) {
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(mHardwareBuffer, &description);
- return Frame(description.width, description.height, 0);
- } else {
- LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
- "getFrame() called on a context with no surface!");
- return vulkanManager().dequeueNextBuffer(mVkSurface);
- }
+ LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
+ return vulkanManager().dequeueNextBuffer(mVkSurface);
}
IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
@@ -217,6 +210,10 @@ void SkiaVulkanPipeline::onContextDestroyed() {
}
}
+const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const {
+ return mVkSurface->getPixelSnapMatrix();
+}
+
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index d921ddb0d0fb..284cde537ec0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -53,8 +53,8 @@ public:
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
- bool supportsExtendedRangeHdr() const override { return true; }
void setTargetSdrHdrRatio(float ratio) override;
+ const SkM44& getPixelSnapMatrix() const override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 23611efccd73..7e9d44fbdbd1 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -117,12 +117,8 @@ void CacheManager::trimMemory(TrimLevel mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(/*syncCpu=*/true);
- if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
- mode = TrimLevel::COMPLETE;
- }
-
switch (mode) {
- case TrimLevel::COMPLETE:
+ case TrimLevel::BACKGROUND:
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
mRenderThread.destroyRenderingContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f10b2b2f0694..f60c1f3c6ad8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -236,7 +236,6 @@ void CanvasContext::setupPipelineSurface() {
if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) {
setBufferCount(mNativeSurface->getNativeWindow());
-
}
mFrameNumber = 0;
@@ -301,17 +300,13 @@ void CanvasContext::setOpaque(bool opaque) {
float CanvasContext::setColorMode(ColorMode mode) {
if (mode != mColorMode) {
- const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
- if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
- mode = ColorMode::WideColorGamut;
- }
mColorMode = mode;
mRenderPipeline->setSurfaceColorProperties(mode);
setupPipelineSurface();
}
switch (mColorMode) {
case ColorMode::Hdr:
- return 3.f; // TODO: Refine this number
+ return Properties::maxHdrHeadroomOn8bit;
case ColorMode::Hdr10:
return 10.f;
default:
@@ -528,6 +523,14 @@ void CanvasContext::notifyFramePending() {
sendLoadResetHint();
}
+Frame CanvasContext::getFrame() {
+ if (mHardwareBuffer != nullptr) {
+ return {mBufferParams.getLogicalWidth(), mBufferParams.getLogicalHeight(), 0};
+ } else {
+ return mRenderPipeline->getFrame();
+ }
+}
+
void CanvasContext::draw() {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
@@ -569,7 +572,8 @@ void CanvasContext::draw() {
mCurrentFrameInfo->markIssueDrawCommandsStart();
- Frame frame = mRenderPipeline->getFrame();
+ Frame frame = getFrame();
+
SkRect windowDirty = computeDirtyRect(frame, &dirty);
ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
@@ -862,6 +866,10 @@ SkISize CanvasContext::getNextFrameSize() const {
return size;
}
+const SkM44& CanvasContext::getPixelSnapMatrix() const {
+ return mRenderPipeline->getPixelSnapMatrix();
+}
+
void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b26c018e86fb..d7215de92375 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -200,6 +200,9 @@ public:
SkISize getNextFrameSize() const;
+ // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center
+ const SkM44& getPixelSnapMatrix() const;
+
// Called when SurfaceStats are available.
static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
ASurfaceControlStats* stats);
@@ -264,6 +267,8 @@ private:
FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
+ Frame getFrame();
+
// The same type as Frame.mWidth and Frame.mHeight
int32_t mLastFrameWidth = 0;
int32_t mLastFrameHeight = 0;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 4fb114b71bf5..94f35fd9eaf2 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -423,6 +423,7 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE};
EGLConfig config = mEglConfig;
+ bool overrideWindowDataSpaceForHdr = false;
if (colorMode == ColorMode::A8) {
// A8 doesn't use a color space
if (!mEglConfigA8) {
@@ -450,12 +451,13 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
- // Extended Range HDR requires being able to manipulate the dataspace in ways
- // we cannot easily do while going through EGLSurface. Given this requires
- // composer3 support, just treat HDR as equivalent to wide color gamut if
- // the GLES path is still being hit
+ // We don't have an EGL colorspace for extended range P3 that's used for HDR
+ // So override it after configuring the EGL context
case ColorMode::Hdr:
case ColorMode::Hdr10:
+ overrideWindowDataSpaceForHdr = true;
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ break;
case ColorMode::WideColorGamut: {
skcms_Matrix3x3 colorGamut;
LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
@@ -491,6 +493,16 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
(void*)window, eglErrorString());
}
+ if (overrideWindowDataSpaceForHdr) {
+ // This relies on knowing that EGL will not re-set the dataspace after the call to
+ // eglCreateWindowSurface. Since the handling of the colorspace extension is largely
+ // implemented in libEGL in the platform, we can safely assume this is the case
+ int32_t err = ANativeWindow_setBuffersDataSpace(
+ window,
+ static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED));
+ LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err);
+ }
+
return surface;
}
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
index 91fe3f6cf273..8c942d0fa102 100644
--- a/libs/hwui/renderthread/HardwareBufferRenderParams.h
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -36,9 +36,12 @@ class RenderProxy;
class HardwareBufferRenderParams {
public:
HardwareBufferRenderParams() = default;
- HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
+ HardwareBufferRenderParams(int32_t logicalWidth, int32_t logicalHeight,
+ const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
RenderCallback&& callback)
- : mTransform(transform)
+ : mLogicalWidth(logicalWidth)
+ , mLogicalHeight(logicalHeight)
+ , mTransform(transform)
, mColorSpace(colorSpace)
, mRenderCallback(std::move(callback)) {}
const SkMatrix& getTransform() const { return mTransform; }
@@ -50,7 +53,12 @@ public:
}
}
+ int32_t getLogicalWidth() { return mLogicalWidth; }
+ int32_t getLogicalHeight() { return mLogicalHeight; }
+
private:
+ int32_t mLogicalWidth;
+ int32_t mLogicalHeight;
SkMatrix mTransform = SkMatrix::I();
sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB();
RenderCallback mRenderCallback = nullptr;
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index c68fcdfc76f2..6c2cb9d71c55 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -95,8 +95,8 @@ public:
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
- virtual bool supportsExtendedRangeHdr() const { return false; }
virtual void setTargetSdrHdrRatio(float ratio) = 0;
+ virtual const SkM44& getPixelSnapMatrix() const = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7a7f1abdd268..0afd949cf5c9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -135,7 +135,9 @@ void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t
!mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
- nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+
+ nsecs_t timeUntilDeadline = frameDeadline - frameTimeNanos;
+ nsecs_t runAt = (frameTimeNanos + (timeUntilDeadline * 0.25f));
queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
}
}
@@ -257,7 +259,6 @@ void RenderThread::initThreadLocals() {
void RenderThread::setupFrameInterval() {
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
mTimeLord.setFrameInterval(frameIntervalNanos);
- mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}
void RenderThread::requireGlContext() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 0a89e5e944f8..c77cd4134d1e 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -235,7 +235,6 @@ private:
bool mFrameCallbackTaskPending;
TimeLord mTimeLord;
- nsecs_t mDispatchFrameDelay = 4_ms;
RenderState* mRenderState;
EglManager* mEglManager;
WebViewFunctorManager& mFunctorManager;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 718d4a16d5c8..96bfc1061d4e 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -42,6 +42,36 @@ namespace android {
namespace uirenderer {
namespace renderthread {
+static std::array<std::string_view, 18> sEnableExtensions{
+ VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
+ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
+ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE1_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE2_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE3_EXTENSION_NAME,
+ VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
+ VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
+ VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
+ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
+ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+};
+
+static bool shouldEnableExtension(const std::string_view& extension) {
+ for (const auto& it : sEnableExtensions) {
+ if (it == extension) {
+ return true;
+ }
+ }
+ return false;
+}
+
static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
// All Vulkan structs that could be part of the features chain will start with the
// structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
@@ -139,6 +169,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
bool hasKHRSurfaceExtension = false;
bool hasKHRAndroidSurfaceExtension = false;
for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) {
+ if (!shouldEnableExtension(extension.extensionName)) {
+ ALOGV("Not enabling instance extension %s", extension.extensionName);
+ continue;
+ }
+ ALOGV("Enabling instance extension %s", extension.extensionName);
mInstanceExtensions.push_back(extension.extensionName);
if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
hasKHRSurfaceExtension = true;
@@ -219,6 +254,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err);
bool hasKHRSwapchainExtension = false;
for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) {
+ if (!shouldEnableExtension(extension.extensionName)) {
+ ALOGV("Not enabling device extension %s", extension.extensionName);
+ continue;
+ }
+ ALOGV("Enabling device extension %s", extension.extensionName);
mDeviceExtensions.push_back(extension.extensionName);
if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
hasKHRSwapchainExtension = true;
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 21b6c44e997e..10f456745147 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -63,6 +63,18 @@ static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) {
return SkMatrix::I();
}
+static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform);
+ SkMatrix invert;
+ LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert));
+ return SkM44::Translate(kOffset, kOffset)
+ .postConcat(SkM44(preRotation))
+ .preConcat(SkM44(invert));
+}
+
static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
ATRACE_CALL();
@@ -178,6 +190,8 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
outWindowInfo->preTransform =
GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
+ outWindowInfo->pixelSnapMatrix =
+ GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform);
err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
if (err != 0 || query_value < 0) {
@@ -413,6 +427,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
}
mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
+ mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform);
}
uint32_t idx;
@@ -438,9 +453,15 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx];
if (bufferInfo->skSurface.get() == nullptr) {
+ SkSurfaceProps surfaceProps;
+ if (mWindowInfo.colorMode != ColorMode::Default) {
+ surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
+ surfaceProps.pixelGeometry());
+ }
bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
- kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
+ kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
+ /*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index e2ddc6b07768..6f5280105e55 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -47,6 +47,7 @@ public:
const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; }
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
+ const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
private:
/*
@@ -105,6 +106,7 @@ private:
SkISize actualSize;
// transform to be applied to the SkSurface to map the coordinates to the provided transform
SkMatrix preTransform;
+ SkM44 pixelSnapMatrix;
};
VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext);
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 24cfc9d70c9b..c3ad7670d473 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -53,8 +53,6 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context)
mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
mLocked.resourcesLoaded = false;
-
- mLocked.buttonState = 0;
}
MouseCursorController::~MouseCursorController() {
@@ -63,24 +61,23 @@ MouseCursorController::~MouseCursorController() {
mLocked.pointerSprite.clear();
}
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
std::scoped_lock lock(mLock);
- return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+ return getBoundsLocked();
}
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
if (!mLocked.viewport.isValid()) {
- return false;
+ return {};
}
- *outMinX = mLocked.viewport.logicalLeft;
- *outMinY = mLocked.viewport.logicalTop;
- *outMaxX = mLocked.viewport.logicalRight - 1;
- *outMaxY = mLocked.viewport.logicalBottom - 1;
- return true;
+ return FloatRect{
+ static_cast<float>(mLocked.viewport.logicalLeft),
+ static_cast<float>(mLocked.viewport.logicalTop),
+ static_cast<float>(mLocked.viewport.logicalRight - 1),
+ static_cast<float>(mLocked.viewport.logicalBottom - 1),
+ };
}
void MouseCursorController::move(float deltaX, float deltaY) {
@@ -96,22 +93,6 @@ void MouseCursorController::move(float deltaX, float deltaY) {
setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
}
-void MouseCursorController::setButtonState(int32_t buttonState) {
-#if DEBUG_MOUSE_CURSOR_UPDATES
- ALOGD("Set button state 0x%08x", buttonState);
-#endif
- std::scoped_lock lock(mLock);
-
- if (mLocked.buttonState != buttonState) {
- mLocked.buttonState = buttonState;
- }
-}
-
-int32_t MouseCursorController::getButtonState() const {
- std::scoped_lock lock(mLock);
- return mLocked.buttonState;
-}
-
void MouseCursorController::setPosition(float x, float y) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
@@ -121,31 +102,19 @@ void MouseCursorController::setPosition(float x, float y) {
}
void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- if (x <= minX) {
- mLocked.pointerX = minX;
- } else if (x >= maxX) {
- mLocked.pointerX = maxX;
- } else {
- mLocked.pointerX = x;
- }
- if (y <= minY) {
- mLocked.pointerY = minY;
- } else if (y >= maxY) {
- mLocked.pointerY = maxY;
- } else {
- mLocked.pointerY = y;
- }
- updatePointerLocked();
- }
+ const auto bounds = getBoundsLocked();
+ if (!bounds) return;
+
+ mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+ mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+ updatePointerLocked();
}
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
std::scoped_lock lock(mLock);
- *outX = mLocked.pointerX;
- *outY = mLocked.pointerY;
+ return {mLocked.pointerX, mLocked.pointerY};
}
int32_t MouseCursorController::getDisplayId() const {
@@ -235,10 +204,9 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
// Reset cursor position to center if size or display changed.
if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
oldDisplayHeight != newDisplayHeight) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- mLocked.pointerX = (minX + maxX) * 0.5f;
- mLocked.pointerY = (minY + maxY) * 0.5f;
+ if (const auto bounds = getBoundsLocked(); bounds) {
+ mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+ mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index db0ab56429b2..00dc0854440e 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,12 +43,10 @@ public:
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBounds() const;
void move(float deltaX, float deltaY);
- void setButtonState(int32_t buttonState);
- int32_t getButtonState() const;
void setPosition(float x, float y);
- void getPosition(float* outX, float* outY) const;
+ FloatPoint getPosition() const;
int32_t getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
@@ -96,13 +94,11 @@ private:
PointerIconStyle requestedPointerType;
PointerIconStyle resolvedPointerType;
- int32_t buttonState;
-
bool animating{false};
} mLocked GUARDED_BY(mLock);
- bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBoundsLocked() const;
void setPositionLocked(float x, float y);
void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index fedf58d7c6d0..88e351963148 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -114,16 +114,15 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
- mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
}
std::mutex& PointerController::getLock() const {
return mDisplayInfoListener->mLock;
}
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
- return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+ return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
@@ -137,14 +136,6 @@ void PointerController::move(float deltaX, float deltaY) {
mCursorController.move(transformed.x, transformed.y);
}
-void PointerController::setButtonState(int32_t buttonState) {
- mCursorController.setButtonState(buttonState);
-}
-
-int32_t PointerController::getButtonState() const {
- return mCursorController.getButtonState();
-}
-
void PointerController::setPosition(float x, float y) {
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
@@ -156,15 +147,13 @@ void PointerController::setPosition(float x, float y) {
mCursorController.setPosition(transformed.x, transformed.y);
}
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
const int32_t displayId = mCursorController.getDisplayId();
- mCursorController.getPosition(outX, outY);
+ const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
const auto& transform = getTransformForDisplayLocked(displayId);
- const auto xy = transform.inverse().transform(*outX, *outY);
- *outX = xy.x;
- *outY = xy.y;
+ return FloatPoint{transform.inverse().transform(p.x, p.y)};
}
}
@@ -262,19 +251,31 @@ void PointerController::reloadPointerResources() {
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- std::scoped_lock lock(getLock());
+ struct PointerDisplayChangeArgs {
+ int32_t displayId;
+ FloatPoint cursorPosition;
+ };
+ std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
- bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER ||
- mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
- getAdditionalMouseResources = true;
- }
- mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
- if (viewport.displayId != mLocked.pointerDisplayId) {
- float xPos, yPos;
- mCursorController.getPosition(&xPos, &yPos);
- mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
- mLocked.pointerDisplayId = viewport.displayId;
+ { // acquire lock
+ std::scoped_lock lock(getLock());
+
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+ getAdditionalMouseResources = true;
+ }
+ mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ mLocked.pointerDisplayId = viewport.displayId;
+ pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+ }
+ } // release lock
+
+ if (pointerDisplayChanged) {
+ // Notify the policy without holding the pointer controller lock.
+ mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+ pointerDisplayChanged->cursorPosition);
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 48d5a5756a69..ca14b6e9bfdc 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,21 +50,19 @@ public:
~PointerController() override;
- virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
- virtual void move(float deltaX, float deltaY);
- virtual void setButtonState(int32_t buttonState);
- virtual int32_t getButtonState() const;
- virtual void setPosition(float x, float y);
- virtual void getPosition(float* outX, float* outY) const;
- virtual int32_t getDisplayId() const;
- virtual void fade(Transition transition);
- virtual void unfade(Transition transition);
- virtual void setDisplayViewport(const DisplayViewport& viewport);
-
- virtual void setPresentation(Presentation presentation);
- virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId);
- virtual void clearSpots();
+ std::optional<FloatRect> getBounds() const override;
+ void move(float deltaX, float deltaY) override;
+ void setPosition(float x, float y) override;
+ FloatPoint getPosition() const override;
+ int32_t getDisplayId() const override;
+ void fade(Transition transition) override;
+ void unfade(Transition transition) override;
+ void setDisplayViewport(const DisplayViewport& viewport) override;
+
+ void setPresentation(Presentation presentation) override;
+ void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId) override;
+ void clearSpots() override;
void updatePointerIcon(PointerIconStyle iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 96d83a5f0d15..f6f5d3bc51bd 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -81,7 +81,7 @@ public:
virtual PointerIconStyle getDefaultPointerIconId() = 0;
virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index c820d0007a4b..2378d42793a1 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -60,7 +60,7 @@ public:
virtual PointerIconStyle getDefaultPointerIconId() override;
virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
@@ -143,8 +143,8 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic
}
void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
- float /*xPos*/,
- float /*yPos*/) {
+ const FloatPoint& /*position*/
+) {
latestPointerDisplayId = displayId;
}