summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java173
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java17
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java16
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java10
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/Android.bp52
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml32
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/AndroidTest.xml31
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java47
-rw-r--r--libs/WindowManager/Shell/Android.bp30
-rw-r--r--libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml20
-rw-r--r--libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml20
-rw-r--r--libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml20
-rw-r--r--libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml20
-rw-r--r--libs/WindowManager/Shell/res/color/compat_background_ripple.xml (renamed from libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml)0
-rw-r--r--libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml19
-rw-r--r--libs/WindowManager/Shell/res/color/split_divider_background.xml2
-rw-r--r--libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml19
-rw-r--r--libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml23
-rw-r--r--libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml33
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml53
-rw-r--r--libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml29
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_collapse.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_expand.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml27
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml4
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml (renamed from libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml)7
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml22
-rw-r--r--libs/WindowManager/Shell/res/layout/background_panel.xml26
-rw-r--r--libs/WindowManager/Shell/res/layout/badged_image_view.xml55
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml5
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml9
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_mode_hint.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml38
-rw-r--r--libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml40
-rw-r--r--libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml122
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml152
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml47
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml21
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml32
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings_tv.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings_tv.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-land/styles.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings_tv.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml22
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings_tv.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-television/config.xml46
-rw-r--r--libs/WindowManager/Shell/res/values-television/dimen.xml24
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/attrs.xml22
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml26
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml26
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml65
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml28
-rw-r--r--libs/WindowManager/Shell/res/values/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java185
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java253
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java496
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java216
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java184
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java237
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java111
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java201
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java198
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java368
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java393
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java199
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java260
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java145
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java337
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java246
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java272
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java338
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java721
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java406
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java162
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java461
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt741
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java446
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java356
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java145
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java151
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java848
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java187
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java557
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java56
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt28
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt92
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt41
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt64
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt41
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt96
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java85
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java123
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java225
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java172
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java137
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java294
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java128
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java293
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java110
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java431
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java71
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java149
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt469
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java60
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java96
-rw-r--r--libs/androidfw/AssetManager2.cpp63
-rw-r--r--libs/androidfw/LoadedArsc.cpp22
-rw-r--r--libs/androidfw/Locale.cpp43
-rw-r--r--libs/androidfw/ResourceTypes.cpp23
-rw-r--r--libs/androidfw/TEST_MAPPING3
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h12
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp8
-rw-r--r--libs/androidfw/tests/Config_test.cpp3
-rw-r--r--libs/androidfw/tests/PosixUtils_test.cpp4
-rw-r--r--libs/hwui/Android.bp14
-rw-r--r--libs/hwui/ColorMode.h2
-rw-r--r--libs/hwui/DeferredLayerUpdater.cpp39
-rw-r--r--libs/hwui/DeferredLayerUpdater.h4
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/FrameMetricsObserver.h28
-rw-r--r--libs/hwui/FrameMetricsReporter.cpp56
-rw-r--r--libs/hwui/FrameMetricsReporter.h23
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp63
-rw-r--r--libs/hwui/HardwareBitmapUploader.h4
-rw-r--r--libs/hwui/JankTracker.cpp32
-rw-r--r--libs/hwui/JankTracker.h3
-rw-r--r--libs/hwui/Layer.cpp6
-rw-r--r--libs/hwui/Layer.h36
-rw-r--r--libs/hwui/Outline.h10
-rw-r--r--libs/hwui/ProfileData.cpp1
-rw-r--r--libs/hwui/Properties.cpp3
-rw-r--r--libs/hwui/Readback.cpp23
-rw-r--r--libs/hwui/Readback.h3
-rw-r--r--libs/hwui/RecordingCanvas.cpp25
-rw-r--r--libs/hwui/RecordingCanvas.h2
-rw-r--r--libs/hwui/RenderProperties.h6
-rw-r--r--libs/hwui/SkiaCanvas.cpp55
-rw-r--r--libs/hwui/SkiaCanvas.h26
-rw-r--r--libs/hwui/TreeInfo.h2
-rw-r--r--libs/hwui/VectorDrawable.cpp10
-rw-r--r--libs/hwui/VectorDrawable.h2
-rw-r--r--libs/hwui/WebViewFunctorManager.cpp39
-rw-r--r--libs/hwui/WebViewFunctorManager.h1
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp42
-rw-r--r--libs/hwui/apex/android_bitmap.cpp7
-rw-r--r--libs/hwui/apex/include/android/graphics/renderthread.h34
-rw-r--r--libs/hwui/apex/renderthread.cpp25
-rw-r--r--libs/hwui/canvas/CanvasFrontend.cpp97
-rw-r--r--libs/hwui/canvas/CanvasFrontend.h76
-rw-r--r--libs/hwui/effects/StretchEffect.cpp4
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp1
-rw-r--r--libs/hwui/hwui/Bitmap.cpp4
-rw-r--r--libs/hwui/hwui/BlurDrawLooper.cpp2
-rw-r--r--libs/hwui/hwui/BlurDrawLooper.h10
-rw-r--r--libs/hwui/hwui/Canvas.h10
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp3
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp10
-rw-r--r--libs/hwui/hwui/MinikinUtils.h4
-rw-r--r--libs/hwui/hwui/Paint.h17
-rw-r--r--libs/hwui/hwui/PaintFilter.h5
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp32
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp55
-rwxr-xr-xlibs/hwui/jni/Bitmap.cpp2
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp11
-rw-r--r--libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp10
-rw-r--r--libs/hwui/jni/GIFMovie.cpp11
-rw-r--r--libs/hwui/jni/Graphics.cpp32
-rw-r--r--libs/hwui/jni/GraphicsJNI.h21
-rw-r--r--libs/hwui/jni/NinePatch.cpp2
-rw-r--r--libs/hwui/jni/Paint.cpp90
-rw-r--r--libs/hwui/jni/PaintFilter.cpp4
-rw-r--r--libs/hwui/jni/RenderEffect.cpp45
-rw-r--r--libs/hwui/jni/Shader.cpp121
-rw-r--r--libs/hwui/jni/Utils.cpp21
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.cpp4
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp72
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp109
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp31
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRendererObserver.h3
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp94
-rw-r--r--libs/hwui/jni/text/MeasuredText.cpp58
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp1
-rw-r--r--libs/hwui/libhwui.map.txt3
-rw-r--r--libs/hwui/pipeline/skia/AnimatedDrawables.h2
-rw-r--r--libs/hwui/pipeline/skia/DumpOpsCanvas.h2
-rw-r--r--libs/hwui/pipeline/skia/LayerDrawable.cpp187
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp4
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp4
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp24
-rw-r--r--libs/hwui/pipeline/skia/VkFunctorDrawable.cpp1
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp2
-rw-r--r--libs/hwui/private/hwui/DrawVkInfo.h3
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp114
-rw-r--r--libs/hwui/renderthread/CanvasContext.h63
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp15
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h17
-rw-r--r--libs/hwui/renderthread/EglManager.cpp136
-rw-r--r--libs/hwui/renderthread/EglManager.h2
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp25
-rw-r--r--libs/hwui/renderthread/RenderProxy.h8
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp69
-rw-r--r--libs/hwui/renderthread/RenderThread.h9
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp3
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp13
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/PathClippingAnimation.cpp153
-rw-r--r--libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp31
-rw-r--r--libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp2
-rw-r--r--libs/hwui/tests/macrobench/TestSceneRunner.cpp3
-rw-r--r--libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp5
-rw-r--r--libs/hwui/tests/unit/FrameMetricsReporterTests.cpp211
-rw-r--r--libs/hwui/tests/unit/JankTrackerTests.cpp35
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp2
-rw-r--r--libs/hwui/tests/unit/SkiaBehaviorTests.cpp6
-rw-r--r--libs/hwui/tests/unit/SkiaPipelineTests.cpp4
-rw-r--r--libs/hwui/utils/Color.cpp8
-rw-r--r--libs/hwui/utils/PaintUtils.h11
-rw-r--r--libs/input/Android.bp1
-rw-r--r--libs/input/PointerController.cpp148
-rw-r--r--libs/input/PointerController.h50
-rw-r--r--libs/input/SpriteController.cpp29
-rw-r--r--libs/input/SpriteController.h8
-rw-r--r--libs/input/tests/PointerController_test.cpp32
-rw-r--r--libs/input/tests/mocks/MockSpriteController.h3
-rw-r--r--libs/services/Android.bp1
592 files changed, 19968 insertions, 5357 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 8733c152dca9..921552b6cfbb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -208,7 +208,7 @@ public final class CommonFoldingFeature {
return mType;
}
- /** Returns the state of the feature, or {@code null} if the feature has no state. */
+ /** Returns the state of the feature.*/
@State
public int getState() {
return mState;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
index f2e403b4f792..d923a46c3b5d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
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 fc955927f3ed..418ff0e7263a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -58,14 +59,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Currently applied split configuration.
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
- private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
- private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ /**
+ * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
+ * below it.
+ * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
+ * organizer.
+ */
+ private final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
// Callback to Jetpack to notify about changes to split states.
private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
// We currently only support split activity embedding within the one root Task.
+ // TODO(b/207720388): move to TaskContainer
private final Rect mParentBounds = new Rect();
public SplitController() {
@@ -244,7 +251,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
} else {
// Put activity into a new expanded container
- final TaskFragmentContainer newContainer = newContainer(launchedActivity);
+ final TaskFragmentContainer newContainer = newContainer(launchedActivity,
+ launchedActivity.getTaskId());
mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
launchedActivity);
}
@@ -327,12 +335,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
- for (TaskFragmentContainer container : mContainers) {
- if (container.hasActivity(activityToken)) {
- return container;
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ for (TaskFragmentContainer container : containers) {
+ if (container.hasActivity(activityToken)) {
+ return container;
+ }
}
}
-
return null;
}
@@ -340,9 +350,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Creates and registers a new organized container with an optional activity that will be
* re-parented to it in a WCT.
*/
- TaskFragmentContainer newContainer(@Nullable Activity activity) {
- TaskFragmentContainer container = new TaskFragmentContainer(activity);
- mContainers.add(container);
+ TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
+ final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
+ if (!mTaskContainers.contains(taskId)) {
+ mTaskContainers.put(taskId, new TaskContainer());
+ }
+ mTaskContainers.get(taskId).mContainers.add(container);
return container;
}
@@ -354,13 +367,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule) {
- SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
+ final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
secondaryContainer, splitRule);
// Remove container later to prevent pinning escaping toast showing in lock task mode.
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
}
- mSplitContainers.add(splitContainer);
+ mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer);
}
/**
@@ -368,15 +381,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
void removeContainer(@NonNull TaskFragmentContainer container) {
// Remove all split containers that included this one
- mContainers.remove(container);
- List<SplitContainer> containersToRemove = new ArrayList<>();
- for (SplitContainer splitContainer : mSplitContainers) {
+ final int taskId = container.getTaskId();
+ final TaskContainer taskContainer = mTaskContainers.get(taskId);
+ if (taskContainer == null) {
+ return;
+ }
+ taskContainer.mContainers.remove(container);
+ if (taskContainer.mContainers.isEmpty()) {
+ mTaskContainers.remove(taskId);
+ // No more TaskFragment in this Task, so no need to check split container.
+ return;
+ }
+
+ final List<SplitContainer> containersToRemove = new ArrayList<>();
+ for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
if (container.equals(splitContainer.getSecondaryContainer())
|| container.equals(splitContainer.getPrimaryContainer())) {
containersToRemove.add(splitContainer);
}
}
- mSplitContainers.removeAll(containersToRemove);
+ taskContainer.mSplitContainers.removeAll(containersToRemove);
}
/**
@@ -399,13 +423,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
- * Returns the topmost not finished container.
+ * Returns the topmost not finished container in Task of given task id.
*/
@Nullable
- TaskFragmentContainer getTopActiveContainer() {
- for (int i = mContainers.size() - 1; i >= 0; i--) {
- TaskFragmentContainer container = mContainers.get(i);
- if (!container.isFinished() && container.getTopNonFinishingActivity() != null) {
+ TaskFragmentContainer getTopActiveContainer(int taskId) {
+ final TaskContainer taskContainer = mTaskContainers.get(taskId);
+ if (taskContainer == null) {
+ return null;
+ }
+ for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ if (!container.isFinished() && container.getRunningActivityCount() > 0) {
return container;
}
}
@@ -434,7 +462,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitContainer == null) {
return;
}
- if (splitContainer != mSplitContainers.get(mSplitContainers.size() - 1)) {
+ final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
+ .mSplitContainers;
+ if (splitContainers == null
+ || splitContainer != splitContainers.get(splitContainers.size() - 1)) {
// Skip position update - it isn't the topmost split.
return;
}
@@ -455,8 +486,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@Nullable
private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
- for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
- SplitContainer splitContainer = mSplitContainers.get(i);
+ final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
+ .mSplitContainers;
+ if (splitContainers == null) {
+ return null;
+ }
+ for (int i = splitContainers.size() - 1; i >= 0; i--) {
+ final SplitContainer splitContainer = splitContainers.get(i);
if (container.equals(splitContainer.getSecondaryContainer())
|| container.equals(splitContainer.getPrimaryContainer())) {
return splitContainer;
@@ -473,8 +509,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer,
@NonNull TaskFragmentContainer secondContainer) {
- for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
- SplitContainer splitContainer = mSplitContainers.get(i);
+ final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId())
+ .mSplitContainers;
+ if (splitContainers == null) {
+ return null;
+ }
+ for (int i = splitContainers.size() - 1; i >= 0; i--) {
+ final SplitContainer splitContainer = splitContainers.get(i);
final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
if ((firstContainer == secondary && secondContainer == primary)
@@ -500,6 +541,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(
activity.getActivityToken());
+ // Don't launch placeholder if the container is occluded.
+ if (container != null && container != getTopActiveContainer(container.getTaskId())) {
+ return false;
+ }
SplitContainer splitContainer = container != null ? getActiveSplitForContainer(container)
: null;
@@ -584,24 +629,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
private List<SplitInfo> getActiveSplitStates() {
List<SplitInfo> splitStates = new ArrayList<>();
- for (SplitContainer container : mSplitContainers) {
- if (container.getPrimaryContainer().isEmpty()
- || container.getSecondaryContainer().isEmpty()) {
- // We are in an intermediate state because either the split container is about to be
- // removed or the primary or secondary container are about to receive an activity.
- return null;
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
+ .mSplitContainers;
+ for (SplitContainer container : splitContainers) {
+ if (container.getPrimaryContainer().isEmpty()
+ || container.getSecondaryContainer().isEmpty()) {
+ // We are in an intermediate state because either the split container is about
+ // to be removed or the primary or secondary container are about to receive an
+ // activity.
+ return null;
+ }
+ final ActivityStack primaryContainer = container.getPrimaryContainer()
+ .toActivityStack();
+ final ActivityStack secondaryContainer = container.getSecondaryContainer()
+ .toActivityStack();
+ final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
+ // Splits that are not showing side-by-side are reported as having 0 split
+ // ratio, since by definition in the API the primary container occupies no
+ // width of the split when covered by the secondary.
+ mPresenter.shouldShowSideBySide(container)
+ ? container.getSplitRule().getSplitRatio()
+ : 0.0f);
+ splitStates.add(splitState);
}
- ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
- ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
- SplitInfo splitState = new SplitInfo(primaryContainer,
- secondaryContainer,
- // Splits that are not showing side-by-side are reported as having 0 split
- // ratio, since by definition in the API the primary container occupies no
- // width of the split when covered by the secondary.
- mPresenter.shouldShowSideBySide(container)
- ? container.getSplitRule().getSplitRatio()
- : 0.0f);
- splitStates.add(splitState);
}
return splitStates;
}
@@ -611,11 +662,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* the client.
*/
private boolean allActivitiesCreated() {
- for (TaskFragmentContainer container : mContainers) {
- if (container.getInfo() == null
- || container.getInfo().getActivities().size()
- != container.collectActivities().size()) {
- return false;
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ for (TaskFragmentContainer container : containers) {
+ if (container.getInfo() == null
+ || container.getInfo().getActivities().size()
+ != container.collectActivities().size()) {
+ return false;
+ }
}
}
return true;
@@ -629,7 +683,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (container == null) {
return false;
}
- for (SplitContainer splitContainer : mSplitContainers) {
+ final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
+ .mSplitContainers;
+ if (splitContainers == null) {
+ return true;
+ }
+ for (SplitContainer splitContainer : splitContainers) {
if (container.equals(splitContainer.getPrimaryContainer())
|| container.equals(splitContainer.getSecondaryContainer())) {
return false;
@@ -680,9 +739,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
- for (TaskFragmentContainer container : mContainers) {
- if (container.getTaskFragmentToken().equals(fragmentToken)) {
- return container;
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ for (TaskFragmentContainer container : containers) {
+ if (container.getTaskFragmentToken().equals(fragmentToken)) {
+ return container;
+ }
}
}
return null;
@@ -934,6 +996,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Checks if an activity is embedded and its presentation is customized by a
* {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
*/
+ @Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
return mPresenter.isActivityEmbedded(activity.getActivityToken());
}
@@ -964,4 +1027,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Not reuse if it needs to destroy the existing.
return !pairRule.shouldClearTop();
}
+
+ /** Represents TaskFragments and split pairs below a Task. */
+ private static class TaskContainer {
+ final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+ final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ }
}
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 ade573132eef..e7552ff48d52 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -80,7 +80,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
container.finish(shouldFinishDependent, this, wct, mController);
- final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer();
+ final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
+ container.getTaskId());
if (newTopContainer != null) {
mController.updateContainer(wct, newTopContainer);
}
@@ -103,7 +104,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryActivity, primaryRectBounds, null);
// Create new empty task fragment
- final TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(null,
+ primaryContainer.getTaskId());
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
rule, isLtr(primaryActivity, rule));
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -159,7 +161,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Creates a new expanded container.
*/
TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
- final TaskFragmentContainer newContainer = mController.newContainer(null);
+ final TaskFragmentContainer newContainer = mController.newContainer(null,
+ launchingActivity.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction();
createTaskFragment(wct, newContainer.getTaskFragmentToken(),
@@ -180,7 +183,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
TaskFragmentContainer container = mController.getContainerWithActivity(
activity.getActivityToken());
if (container == null || container == containerToAvoid) {
- container = mController.newContainer(activity);
+ container = mController.newContainer(activity, activity.getTaskId());
final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions(
@@ -222,10 +225,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity.getActivityToken());
if (primaryContainer == null) {
- primaryContainer = mController.newContainer(launchingActivity);
+ primaryContainer = mController.newContainer(launchingActivity,
+ launchingActivity.getTaskId());
}
- TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ TaskFragmentContainer secondaryContainer = mController.newContainer(null,
+ primaryContainer.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
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 4d2d0551d828..e49af41d4eac 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -41,6 +43,9 @@ class TaskFragmentContainer {
@NonNull
private final IBinder mToken;
+ /** Parent leaf Task id. */
+ private final int mTaskId;
+
/**
* Server-provided task fragment information.
*/
@@ -71,8 +76,12 @@ class TaskFragmentContainer {
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
*/
- TaskFragmentContainer(@Nullable Activity activity) {
+ TaskFragmentContainer(@Nullable Activity activity, int taskId) {
mToken = new Binder("TaskFragmentContainer");
+ if (taskId == INVALID_TASK_ID) {
+ throw new IllegalArgumentException("Invalid Task id");
+ }
+ mTaskId = taskId;
if (activity != null) {
addPendingAppearedActivity(activity);
}
@@ -275,6 +284,11 @@ class TaskFragmentContainer {
}
}
+ /** Gets the parent leaf Task id. */
+ int getTaskId() {
+ return mTaskId;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index ee8cb48e3c4c..a4fbdbc493f5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -30,6 +30,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -41,7 +42,6 @@ import androidx.window.util.DataProducer;
import androidx.window.util.PriorityDataProducer;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -60,7 +60,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
- new HashMap<>();
+ new ArrayMap<>();
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
@@ -113,7 +113,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
@NonNull
- private Boolean isListeningForLayoutChanges(IBinder token) {
+ private boolean isListeningForLayoutChanges(IBinder token) {
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
if (token.equals(activity.getWindow().getAttributes().token)) {
return true;
@@ -170,8 +170,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* coordinate space and the state is calculated from {@link CommonFoldingFeature#getState()}.
* The state from {@link #mFoldingFeatureProducer} may not be valid since
* {@link #mFoldingFeatureProducer} is a general state controller. If the state is not valid,
- * the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If
- * the bounds are not valid, constructing a {@link FoldingFeature} will throw an
+ * the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If the
+ * bounds are not valid, constructing a {@link FoldingFeature} will throw an
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param activity a proxy for the {@link android.view.Window} that contains the
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
new file mode 100644
index 000000000000..62e8128f9362
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -0,0 +1,52 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "WMJetpackUnitTests",
+
+ srcs: [
+ "**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.window.extensions",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "truth-prebuilt",
+ "testables",
+ "platform-test-annotations",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml
new file mode 100644
index 000000000000..b12b6f6f0ef1
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.window.tests">
+
+ <application android:debuggable="true" android:largeHeap="true">
+ <uses-library android:name="android.test.mock" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for WindowManager Jetpack library"
+ android:targetPackage="androidx.window.tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Jetpack/tests/unittest/AndroidTest.xml b/libs/WindowManager/Jetpack/tests/unittest/AndroidTest.xml
new file mode 100644
index 000000000000..56d8c33fdc09
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<configuration description="Runs Tests for WindowManager Jetpack library">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="WMJetpackUnitTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="WMJetpackUnitTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="androidx.window.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
new file mode 100644
index 000000000000..b6df876e1956
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WindowExtensionsTest {
+ private WindowExtensions mExtensions;
+
+ @Before
+ public void setUp() {
+ mExtensions = WindowExtensionsProvider.getWindowExtensions();
+ }
+
+ @Test
+ public void testGetWindowLayoutComponent() {
+ assertThat(mExtensions.getWindowLayoutComponent()).isNotNull();
+ }
+
+ @Test
+ public void testGetActivityEmbeddingComponent() {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ }
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 9d625c089fc2..7960dec5080b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -43,7 +43,7 @@ filegroup {
name: "wm_shell_util-sources",
srcs: [
"src/com/android/wm/shell/util/**/*.java",
- "src/com/android/wm/shell/common/split/SplitScreenConstants.java"
+ "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
],
path: "src",
}
@@ -74,13 +74,13 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
- "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " +
- "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " +
- "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
- "--loggroups-jar $(location :wm_shell_protolog-groups) " +
- "--output-srcjar $(out) " +
- "$(locations :wm_shell-sources)",
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " +
+ "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " +
+ "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
+ "--loggroups-jar $(location :wm_shell_protolog-groups) " +
+ "--output-srcjar $(out) " +
+ "$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.srcjar"],
}
@@ -92,13 +92,14 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
- "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
- "--loggroups-jar $(location :wm_shell_protolog-groups) " +
- "--viewer-conf $(out) " +
- "$(locations :wm_shell-sources)",
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
+ "--loggroups-jar $(location :wm_shell_protolog-groups) " +
+ "--viewer-conf $(out) " +
+ "$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.json"],
}
+
// End ProtoLog
java_library {
@@ -123,11 +124,12 @@ android_library {
"res",
],
java_resources: [
- ":generate-wm_shell_protolog.json"
+ ":generate-wm_shell_protolog.json",
],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
+ "androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
"androidx.recyclerview_recyclerview",
"kotlinx-coroutines-android",
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
deleted file mode 100644
index 29d9b257cc59..000000000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
deleted file mode 100644
index 70f553b89657..000000000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
deleted file mode 100644
index 29d9b257cc59..000000000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
deleted file mode 100644
index 70f553b89657..000000000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:propertyName="alpha"
- android:valueTo="0"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml b/libs/WindowManager/Shell/res/color/compat_background_ripple.xml
index 329e5b9b31a0..329e5b9b31a0 100644
--- a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/color/compat_background_ripple.xml
diff --git a/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml
new file mode 100644
index 000000000000..43cba1a37bc8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/split_divider_background.xml b/libs/WindowManager/Shell/res/color/split_divider_background.xml
index 329e5b9b31a0..049980803ee3 100644
--- a/libs/WindowManager/Shell/res/color/split_divider_background.xml
+++ b/libs/WindowManager/Shell/res/color/split_divider_background.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
new file mode 100644
index 000000000000..ce8640df0093
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/tv_pip_menu_icon_unfocused" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
new file mode 100644
index 000000000000..6cbf66f00df7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true"
+ android:color="@color/tv_pip_menu_close_icon_bg_focused" />
+ <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
new file mode 100644
index 000000000000..275870450493
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true"
+ android:color="@color/tv_pip_menu_icon_focused" />
+ <item android:state_enabled="false"
+ android:color="@color/tv_pip_menu_icon_disabled" />
+ <item android:color="@color/tv_pip_menu_icon_unfocused" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
new file mode 100644
index 000000000000..4f5e63dac5c0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true"
+ android:color="@color/tv_pip_menu_icon_bg_focused" />
+ <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
new file mode 100644
index 000000000000..1c8cb914af81
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="43dp"
+ android:viewportWidth="48"
+ android:viewportHeight="43">
+ <group>
+ <clip-path
+ android:pathData="M48,43l-48,-0l-0,-43l48,-0z"/>
+ <path
+ android:pathData="M24,43C37.2548,43 48,32.2548 48,19L48,0L0,-0L0,19C0,32.2548 10.7452,43 24,43Z"
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"/>
+ <path
+ android:pathData="M31,12.41L29.59,11L24,16.59L18.41,11L17,12.41L22.59,18L17,23.59L18.41,25L24,19.41L29.59,25L31,23.59L25.41,18L31,12.41Z"
+ android:fillColor="@color/compat_controls_text"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
new file mode 100644
index 000000000000..c81013966c35
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/camera_compat_dismiss_button"/>
+</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
new file mode 100644
index 000000000000..c796b5967f98
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="43dp"
+ android:viewportWidth="48"
+ android:viewportHeight="43">
+ <path
+ android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"/>
+ <path
+ android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M24.6618,22C23.0436,22 21.578,22.6187 20.4483,23.625L18.25,21.375V27H23.7458L21.5353,24.7375C22.3841,24.0125 23.4649,23.5625 24.6618,23.5625C26.8235,23.5625 28.6616,25.0062 29.3028,27L30.75,26.5125C29.9012,23.8938 27.5013,22 24.6618,22Z"
+ android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
new file mode 100644
index 000000000000..3e9fe6dc3b99
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/camera_compat_treatment_applied_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
new file mode 100644
index 000000000000..af505d1cb73c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
@@ -0,0 +1,53 @@
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="43dp"
+ android:viewportWidth="48"
+ android:viewportHeight="43">
+ <path
+ android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"/>
+ <path
+ android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M18,29L18,25.5L19.5,25.5L19.5,29L18,29Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M30,29L30,25.5L28.5,25.5L28.5,29L30,29Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M30,21L30,24.5L28.5,24.5L28.5,21L30,21Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M18,21L18,24.5L19.5,24.5L19.5,21L18,21Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M18,27.5L21.5,27.5L21.5,29L18,29L18,27.5Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M30,27.5L26.5,27.5L26.5,29L30,29L30,27.5Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M30,22.5L26.5,22.5L26.5,21L30,21L30,22.5Z"
+ android:fillColor="@color/compat_controls_text"/>
+ <path
+ android:pathData="M18,22.5L21.5,22.5L21.5,21L18,21L18,22.5Z"
+ android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
new file mode 100644
index 000000000000..c0f1c89b0cbb
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/camera_compat_treatment_suggested_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml
new file mode 100644
index 000000000000..3e1a2bce2393
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/compat_controls_background"/>
+ <corners android:radius="@dimen/letterbox_education_dialog_corner_radius"/>
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
new file mode 100644
index 000000000000..0d8811357c05
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/letterbox_education_accent_primary"/>
+ <corners android:radius="12dp"/>
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
new file mode 100644
index 000000000000..42572d64b96f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/letterbox_education_dismiss_button_background_ripple">
+ <item android:drawable="@drawable/letterbox_education_dismiss_button_background"/>
+</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml
new file mode 100644
index 000000000000..6fcd1de892a3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml
@@ -0,0 +1,29 @@
+<?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="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_accent_primary"
+ android:fillType="evenOdd"
+ android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+ <path
+ android:fillColor="@color/letterbox_education_accent_primary"
+ android:pathData="M 17 14 L 31 14 Q 32 14 32 15 L 32 33 Q 32 34 31 34 L 17 34 Q 16 34 16 33 L 16 15 Q 16 14 17 14 Z" />
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
new file mode 100644
index 000000000000..cbfcfd06e3b7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M26 16L34 24L26 32V16Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
new file mode 100644
index 000000000000..469eb1e14849
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
@@ -0,0 +1,26 @@
+<?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="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
new file mode 100644
index 000000000000..dcb8aed05c9c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_education_dialog_icon_size"
+ android:height="@dimen/letterbox_education_dialog_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:fillType="evenOdd"
+ android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" />
+ <path
+ android:fillColor="@color/letterbox_education_text_secondary"
+ android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" />
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_collapse.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_collapse.xml
new file mode 100644
index 000000000000..63e2a4035cbf
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_collapse.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M12,12V4h2v4.6L20.6,2 22,3.4 15.4,10H20v2zM3.4,22L2,20.6 8.6,14H4v-2h8v8h-2v-4.6z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_expand.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_expand.xml
new file mode 100644
index 000000000000..758b92c4f4da
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_expand.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M3,21v-8h2v4.6L17.6,5H13V3h8v8h-2V6.4L6.4,19H11v2z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
new file mode 100644
index 000000000000..d8f356164358
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M7,10l5,5 5,-5H7z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
new file mode 100644
index 000000000000..3e0011c65942
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M14,7l-5,5 5,5V7z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
new file mode 100644
index 000000000000..f6b3c72e3cb5
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M10,17l5,-5 -5,-5v10z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
new file mode 100644
index 000000000000..1a3446249573
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_menu_focus_border"
+ android:pathData="M7,14l5,-5 5,5H7z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
new file mode 100644
index 000000000000..37f4c87006ba
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M11,5.83L11,10h2L13,5.83l1.83,1.83 1.41,-1.42L12,2 7.76,6.24l1.41,1.42zM17.76,7.76l-1.42,1.41L18.17,11L14,11v2h4.17l-1.83,1.83 1.42,1.41L22,12zM13,18.17L13,14h-2v4.17l-1.83,-1.83 -1.41,1.42L12,22l4.24,-4.24 -1.41,-1.42zM10,13v-2L5.83,11l1.83,-1.83 -1.42,-1.41L2,12l4.24,4.24 1.42,-1.41L5.83,13z"
+ android:fillColor="#FFFFFF" />
+
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index ab74e43472c3..e6ae28207970 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -21,7 +21,9 @@
android:viewportHeight="48">
<path
android:fillColor="@color/compat_controls_background"
- android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"
+ android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
<group
android:translateX="12"
android:translateY="12">
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
index 95decff24ac4..6551edf6d0e6 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/size_compat_background_ripple">
+ android:color="@color/compat_background_ripple">
<item android:drawable="@drawable/size_compat_restart_button"/>
</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
index cce13035dba7..1938f4562e97 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
@@ -14,5 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#9AFFFFFF" android:radius="17dp" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_button_radius" />
+ <solid android:color="@color/tv_pip_menu_icon_bg" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
new file mode 100644
index 000000000000..9bc03112b118
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_border_radius" />
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_focus_border" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/background_panel.xml b/libs/WindowManager/Shell/res/layout/background_panel.xml
new file mode 100644
index 000000000000..c3569d80fa1e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/background_panel.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/background_panel_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal | center_vertical"
+ android:background="@android:color/transparent">
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/badged_image_view.xml b/libs/WindowManager/Shell/res/layout/badged_image_view.xml
new file mode 100644
index 000000000000..5f07121ec7d3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/badged_image_view.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <ImageView
+ android:id="@+id/icon_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@null" />
+
+ <!--
+ Icon badge size is defined in Launcher3 BaseIconFactory as 0.444 of icon size.
+ Constraint guide starts from left, which means for a badge positioned on the right,
+ percent has to be 1 - 0.444 to have the same effect.
+ -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/app_icon_constraint_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.556" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/app_icon_constraint_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.556" />
+
+ <ImageView
+ android:id="@+id/app_icon_view"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@null"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_vertical"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_icon_constraint_horizontal" />
+
+</merge> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
index 76fe3c9bb862..cb516cdbe49b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
@@ -19,9 +19,8 @@
android:id="@+id/bubble_overflow_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/bubble_overflow_padding"
- android:paddingLeft="@dimen/bubble_overflow_padding"
- android:paddingRight="@dimen/bubble_overflow_padding"
+ android:paddingLeft="@dimen/bubble_overflow_container_padding_horizontal"
+ android:paddingRight="@dimen/bubble_overflow_container_padding_horizontal"
android:orientation="vertical"
android:layout_gravity="center_horizontal">
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
index 05b15060946d..78de76a5465b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
@@ -22,7 +22,6 @@
android:orientation="vertical">
<com.android.wm.shell.bubbles.BadgedImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_view"
android:layout_gravity="center"
android:layout_width="@dimen/bubble_size"
@@ -30,16 +29,14 @@
<TextView
android:id="@+id/bubble_view_name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="13sp"
+ android:textSize="14sp"
android:layout_width="@dimen/bubble_name_width"
android:layout_height="wrap_content"
- android:maxLines="1"
- android:lines="2"
+ android:lines="1"
android:ellipsize="end"
android:layout_gravity="center"
android:paddingTop="@dimen/bubble_overflow_text_padding"
android:paddingEnd="@dimen/bubble_overflow_text_padding"
android:paddingStart="@dimen/bubble_overflow_text_padding"
- android:gravity="center"/>
+ android:gravity="center_horizontal|top"/>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
index 87deb8b5a1fd..5c8c84cbb85b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
@@ -21,8 +21,8 @@
android:layout_width="wrap_content"
android:paddingTop="48dp"
android:paddingBottom="48dp"
- android:paddingEnd="16dp"
- android:layout_marginEnd="24dp"
+ android:paddingEnd="@dimen/bubble_user_education_padding_end"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index fafe40e924f5..b28f58f8356d 100644
--- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -23,8 +23,8 @@
android:clickable="true"
android:paddingTop="28dp"
android:paddingBottom="16dp"
- android:paddingEnd="48dp"
- android:layout_marginEnd="24dp"
+ android:paddingEnd="@dimen/bubble_user_education_padding_end"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
index bb48bf7b8b2c..44b2f45052ba 100644
--- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -16,7 +16,7 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipToPadding="false"
@@ -26,7 +26,7 @@
<TextView
android:id="@+id/compat_mode_hint_text"
- android:layout_width="188dp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="4sp"
android:background="@drawable/compat_hint_bubble"
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index dc1683475c48..dfaeeeb81c07 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -21,11 +21,47 @@
android:orientation="vertical"
android:gravity="bottom|end">
+ <include android:id="@+id/camera_compat_hint"
+ android:visibility="gone"
+ android:layout_width="@dimen/camera_compat_hint_width"
+ android:layout_height="wrap_content"
+ layout="@layout/compat_mode_hint"/>
+
+ <LinearLayout
+ android:id="@+id/camera_compat_control"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:layout_marginEnd="@dimen/compat_button_margin"
+ android:layout_marginBottom="@dimen/compat_button_margin"
+ android:orientation="vertical">
+
+ <ImageButton
+ android:id="@+id/camera_compat_treatment_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"/>
+
+ <ImageButton
+ android:id="@+id/camera_compat_dismiss_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/camera_compat_dismiss_ripple"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/camera_compat_dismiss_button_description"/>
+
+ </LinearLayout>
+
<include android:id="@+id/size_compat_hint"
- layout="@layout/compat_mode_hint"/>
+ android:visibility="gone"
+ android:layout_width="@dimen/size_compat_hint_width"
+ android:layout_height="wrap_content"
+ layout="@layout/compat_mode_hint"/>
<ImageButton
android:id="@+id/size_compat_restart_button"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
new file mode 100644
index 000000000000..cd1d99ae58b0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -0,0 +1,40 @@
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/letterbox_education_dialog_action_width"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/letterbox_education_dialog_action_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginBottom="12dp"/>
+
+ <TextView
+ android:id="@+id/letterbox_education_dialog_action_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="4sp"
+ android:textAlignment="center"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="14sp"/>
+
+</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
new file mode 100644
index 000000000000..95923763d889
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -0,0 +1,122 @@
+<!--
+ ~ 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.compatui.letterboxedu.LetterboxEduDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/system_neutral1_900">
+
+ <!-- The background of the top-level layout acts as the background dim. -->
+
+ <!-- Vertical margin will be set dynamically since it depends on task bounds.
+ Setting the alpha of the dialog container to 0, since it shouldn't be visible until the
+ enter animation starts. -->
+ <FrameLayout
+ android:id="@+id/letterbox_education_dialog_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/letterbox_education_dialog_margin"
+ android:background="@drawable/letterbox_education_dialog_background"
+ android:alpha="0"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="@dimen/letterbox_education_dialog_width"
+ app:layout_constrainedHeight="true">
+
+ <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+ corner radius will be cut off when scrolling to the top/bottom. -->
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:padding="24dp">
+
+ <ImageView
+ android:layout_width="@dimen/letterbox_education_dialog_icon_size"
+ android:layout_height="@dimen/letterbox_education_dialog_icon_size"
+ android:layout_marginBottom="12dp"
+ android:src="@drawable/letterbox_education_ic_letterboxed_app"/>
+
+ <TextView
+ android:id="@+id/letterbox_education_dialog_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="4sp"
+ android:text="@string/letterbox_education_dialog_title"
+ android:textAlignment="center"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:lineSpacingExtra="4sp"
+ android:text="@string/letterbox_education_dialog_subtext"
+ android:textAlignment="center"
+ android:textColor="@color/letterbox_education_text_secondary"
+ android:textSize="14sp"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="top"
+ android:orientation="horizontal"
+ android:paddingTop="48dp">
+
+ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:icon="@drawable/letterbox_education_ic_screen_rotation"
+ app:text="@string/letterbox_education_screen_rotation_text"/>
+
+ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart=
+ "@dimen/letterbox_education_dialog_space_between_actions"
+ app:icon="@drawable/letterbox_education_ic_reposition"
+ app:text="@string/letterbox_education_reposition_text"/>
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/letterbox_education_dialog_dismiss_button"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:layout_marginTop="48dp"
+ android:background=
+ "@drawable/letterbox_education_dismiss_button_background_ripple"
+ android:text="@string/letterbox_education_got_it"
+ android:textColor="@android:color/system_neutral1_900"
+ android:textAlignment="center"
+ android:contentDescription="@string/letterbox_education_got_it"/>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ </FrameLayout>
+
+</com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 49e2379589a4..b826d03bf765 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -15,38 +15,124 @@
limitations under the License.
-->
<!-- Layout for TvPipMenuView -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_pip_menu"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#CC000000">
-
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="350dp"
- android:orientation="horizontal"
- android:alpha="0">
-
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
- android:layout_width="@dimen/picture_in_picture_button_width"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
-
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="@dimen/picture_in_picture_button_width"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
-
- <!-- More TvPipMenuActionButtons may be added here at runtime. -->
-
- </LinearLayout>
-
-</FrameLayout>
+ android:layout_height="match_parent">
+
+ <ScrollView
+ android:id="@+id/tv_pip_menu_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:scrollbars="none"
+ android:layout_margin="@dimen/pip_menu_outer_space"
+ android:visibility="gone"/>
+
+ <HorizontalScrollView
+ android:id="@+id/tv_pip_menu_horizontal_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:scrollbars="none"
+ android:layout_margin="@dimen/pip_menu_outer_space">
+
+ <LinearLayout
+ android:id="@+id/tv_pip_menu_action_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:orientation="horizontal"
+ android:alpha="0">
+
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_fullscreen_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_fullscreen_white"
+ android:text="@string/pip_fullscreen" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_move_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_move_white"
+ android:text="@String/pip_move" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_collapse"
+ android:visibility="gone"
+ android:text="@string/pip_collapse" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white"
+ android:text="@string/pip_close" />
+
+ <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <View
+ android:id="@+id/tv_pip_menu_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:alpha="0"
+ android:layout_margin="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_border"/>
+
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_up"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_up" />
+
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_right"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_right" />
+
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_down"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_down" />
+
+ <ImageView
+ android:id="@+id/tv_pip_menu_arrow_left"
+ android:layout_width="@dimen/pip_menu_arrow_size"
+ android:layout_height="@dimen/pip_menu_arrow_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentLeft="true"
+ android:alpha="0"
+ android:elevation="@dimen/pip_menu_arrow_elevation"
+ android:src="@drawable/pip_ic_move_left" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
index 5925008e0d08..a86a14525022 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
@@ -15,36 +15,19 @@
limitations under the License.
-->
<!-- Layout for TvPipMenuActionButton -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/button"
+ android:layout_width="@dimen/pip_menu_button_size"
+ android:layout_height="@dimen/pip_menu_button_size"
+ android:layout_margin="@dimen/pip_menu_button_margin"
+ android:background="@drawable/tv_pip_button_bg"
+ android:focusable="true">
- <ImageView android:id="@+id/button"
- android:layout_width="34dp"
- android:layout_height="34dp"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:focusable="true"
- android:src="@drawable/tv_pip_button_focused"
- android:importantForAccessibility="yes" />
-
- <ImageView android:id="@+id/icon"
- android:layout_width="34dp"
- android:layout_height="34dp"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:padding="5dp"
- android:importantForAccessibility="no" />
-
- <TextView android:id="@+id/desc"
- android:layout_width="100dp"
- android:layout_height="wrap_content"
- android:layout_below="@id/icon"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="3dp"
- android:gravity="center"
- android:text="@string/pip_fullscreen"
- android:alpha="0"
- android:fontFamily="sans-serif"
- android:textSize="12sp"
- android:textColor="#EEEEEE"
- android:importantForAccessibility="no" />
-</merge>
+ <ImageView android:id="@+id/icon"
+ android:layout_width="@dimen/pip_menu_icon_size"
+ android:layout_height="@dimen/pip_menu_icon_size"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:tint="@color/tv_pip_menu_icon" />
+</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
deleted file mode 100644
index bf4eb2691ff0..000000000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.wm.shell.pip.tv.TvPipMenuActionButton
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/picture_in_picture_button_width"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index c3ae053d156d..2476f65c7e5b 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Maak toe"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Vou uit"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellings"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gaan by verdeelde skerm in"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Kieslys"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in beeld-in-beeld"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"As jy nie wil hê dat <xliff:g id="NAME">%s</xliff:g> hierdie kenmerk moet gebruik nie, tik om instellings oop te maak en skakel dit af."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Slaan oor na volgende"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Slaan oor na vorige"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tik om hierdie program te herbegin en maak volskerm oop."</string>
- <string name="got_it" msgid="4428750913636945527">"Het dit"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string>
+ <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="6688664582871779215">"Sommige programme werk beter in portret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van hierdie opsies om jou spasie ten beste te benut"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai jou toestel om dit volskerm te maak"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik langs ’n program om dit te herposisioneer"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
index 6ce588034f9e..c87bec093cca 100644
--- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Skuif PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Vou PIP uit"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Vou PIP in"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Dubbeldruk "<annotation icon="home_icon">" TUIS "</annotation>" vir kontroles"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index c889039cffdb..f0c391cd6b99 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ዝጋ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ዘርጋ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ቅንብሮች"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"የተከፈለ ማያ ገጽን አስገባ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ምናሌ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ወደ ቀጣይ ዝለል"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ወደ ቀዳሚ ዝለል"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string>
<string name="restart_button_description" msgid="5887656107651190519">"ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ እና ወደ ሙሉ ማያ ገጽ ይሂዱ።"</string>
- <string name="got_it" msgid="4428750913636945527">"ገባኝ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"የካሜራ ችግሮች አሉ?\nዳግም ለማበጀት መታ ያድርጉ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"አንዳንድ መተግበሪያዎች በቁም ፎቶ ውስጥ በተሻለ ሁኔታ ይሰራሉ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ቦታዎን በአግባቡ ለመጠቀም ከእነዚህ አማራጮች ውስጥ አንዱን ይሞክሩ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ወደ የሙሉ ገጽ ዕይታ ለመሄድ መሣሪያዎን ያሽከርክሩት"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ቦታውን ለመቀየር ከመተግበሪያው ቀጥሎ ላይ ሁለቴ መታ ያድርጉ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index fcb87c5682e3..d23353858de6 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
+ <string name="pip_move" msgid="1544227837964635439">"ፒአይፒ ውሰድ"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"ፒአይፒን ዘርጋ"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"ፒአይፒን ሰብስብ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ለመቆጣጠሪያዎች "<annotation icon="home_icon">"መነሻ"</annotation>"ን ሁለቴ ይጫኑ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 2c89b1d4eb56..aa4b3b704110 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"إغلاق"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"توسيع"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"الإعدادات"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"الدخول في وضع تقسيم الشاشة"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"القائمة"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"التخطي إلى التالي"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"التخطي إلى السابق"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string>
<string name="restart_button_description" msgid="5887656107651190519">"انقر لإعادة تشغيل هذا التطبيق والانتقال إلى وضع ملء الشاشة."</string>
- <string name="got_it" msgid="4428750913636945527">"حسنًا"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل هناك مشاكل في الكاميرا؟\nانقر لإعادة الضبط."</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"تعمل بعض التطبيقات على أكمل وجه في الشاشات العمودية"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"جرِّب تنفيذ أحد هذه الخيارات للاستفادة من مساحتك إلى أقصى حد."</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"قم بتدوير الشاشة للانتقال إلى وضع ملء الشاشة."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"انقر مرتين بجانب التطبيق لتغيير موضعه."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
index 4eef29e2ed12..a1ceda5fc987 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string>
<string name="pip_close" msgid="9135220303720555525">"‏إغلاق PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string>
+ <string name="pip_move" msgid="1544227837964635439">"‏نقل نافذة داخل النافذة (PIP)"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"‏توسيع نافذة داخل النافذة (PIP)"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"‏تصغير نافذة داخل النافذة (PIP)"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" انقر مرتين على "<annotation icon="home_icon">" الصفحة الرئيسية "</annotation>" للوصول لعناصر التحكم."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 0a74ac6391ce..985d3b9b96fd 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -19,35 +19,38 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pip_phone_close" msgid="5783752637260411309">"বন্ধ কৰক"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"বিস্তাৰ কৰক"</string>
- <string name="pip_phone_settings" msgid="5468987116750491918">"ছেটিংসমূহ"</string>
+ <string name="pip_phone_settings" msgid="5468987116750491918">"ছেটিং"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"বিভাজিত স্ক্ৰীন ম’ডলৈ যাওক"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</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>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"আগৰটো মিডিয়ালৈ যাওক"</string>
<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_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</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="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীণখন সম্পূৰ্ণ স্ক্ৰীণ কৰক"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</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>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীণখন ৩০% কৰক"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীণখন সম্পূৰ্ণ স্ক্ৰীণ কৰক"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীণখন সম্পূৰ্ণ স্ক্ৰীণ কৰক"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীণখন ৭০% কৰক"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীণখন ৫০% কৰক"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীণখন ৩০% কৰক"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীণখন সম্পূৰ্ণ স্ক্ৰীণ কৰক"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটোৰ পৰা বাহিৰ হওক"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ bubblesৰ ছেটিংসমূহ"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ bubblesৰ ছেটিং"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"অভাৰফ্ল’"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"ষ্টেকত পুনৰ যোগ দিয়ক"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g>ৰ পৰা <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
@@ -56,7 +59,7 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"শীৰ্ষৰ সোঁফালে নিয়ক"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"বুটামটো বাওঁফালে নিয়ক"</string>
<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="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ছেটিং"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল অগ্ৰাহ্য কৰক"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"বাৰ্তালাপ বাবল নকৰিব"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string>
<string name="restart_button_description" msgid="5887656107651190519">"এপ্‌টো ৰিষ্টাৰ্ট কৰিবলৈ আৰু পূৰ্ণ স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ টিপক।"</string>
- <string name="got_it" msgid="4428750913636945527">"বুজি পালোঁ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো সমস্যা হৈছে নেকি?\nপুনৰ খাপ খোৱাবলৈ টিপক"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"কিছুমান এপে প’ৰ্ট্ৰেইট ম’ডত বেছি ভালকৈ কাম কৰে"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"আপোনাৰ spaceৰ পৰা পাৰ্যমানে উপকৃত হ’বলৈ ইয়াৰে এটা বিকল্প চেষ্টা কৰি চাওক"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"পূৰ্ণ স্ক্ৰীনলৈ যাবলৈ আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ কাষত দুবাৰ টিপক"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
index 6c223f45d9b3..8d7bd9f6a27e 100644
--- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
@@ -20,5 +20,9 @@
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"চিত্ৰৰ ভিতৰত চিত্ৰ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string>
<string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string>
- <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীণ"</string>
+ <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="pip_move" msgid="1544227837964635439">"পিপ স্থানান্তৰ কৰক"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"পিপ বিস্তাৰ কৰক"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"পিপ সংকোচন কৰক"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" নিয়ন্ত্ৰণৰ বাবে "<annotation icon="home_icon">" গৃহপৃষ্ঠা "</annotation>" বুটামত দুবাৰ হেঁচক"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 54483bfa730c..8cd9b7a635ab 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Bağlayın"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Genişləndirin"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana daxil olun"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> şəkil içində şəkildədir"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> tətbiqinin bu funksiyadan istifadə etməyini istəmirsinizsə, ayarları açmaq və deaktiv etmək üçün klikləyin."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Növbətiyə keçin"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Əvvəlkinə keçin"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
@@ -43,10 +46,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yuxarı 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Aşağı tam ekran"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bir əlli rejimdən istifadə edilir"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Birəlli rejim istifadəsi"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bir əlli rejimi başladın"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Bir əlli rejimdən çıxın"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Birəlli rejim başlasın"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Birəlli rejimdən çıxın"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> yumrucuqları üçün ayarlar"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Kənara çıxma"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Yenidən dəstəyə əlavə edin"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Bu tətbiqi sıfırlayaraq tam ekrana keçmək üçün toxunun."</string>
- <string name="got_it" msgid="4428750913636945527">"Anladım"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string>
+ <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="6688664582871779215">"Bəzi tətbiqlər portret rejimində daha yaxşı işləyir"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Məkanınızdan maksimum yararlanmaq üçün bu seçimlərdən birini sınayın"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana keçmək üçün cihazınızı fırladın"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tətbiqin yerini dəyişmək üçün yanına iki dəfə toxunun"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
index c9f1acbef31b..87c46fa41a01 100644
--- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP tətbiq edin"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP-ni genişləndirin"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP-ni yığcamlaşdırın"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Nizamlayıcılar üçün "<annotation icon="home_icon">" ƏSAS SƏHİFƏ "</annotation>" süçimini iki dəfə bası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 5ed79c4901cd..49524c608543 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Podešavanja"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Uđi na podeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je slika u slici"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da <xliff:g id="NAME">%s</xliff:g> koristi ovu funkciju, dodirnite da biste otvorili podešavanja i isključili je."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Pređi na sledeće"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Pređi na prethodno"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste restartovali aplikaciju i prešli u režim celog ekrana."</string>
- <string name="got_it" msgid="4428750913636945527">"Važi"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string>
+ <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="6688664582871779215">"Neke aplikacije najbolje funkcionišu u uspravnom režimu"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste na najbolji način iskoristili prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotirajte uređaj za prikaz preko celog ekrana"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste promenili njenu poziciju"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
index 6fbc91bbec60..c87f30611a07 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Premesti sliku u slici"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Proširi sliku u slici"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Skupi sliku u slici"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">" HOME "</annotation>" za kontrole"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index a9a62de6c8f9..1767e0d66241 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрыць"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Разгарнуць"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Налады"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Падзяліць экран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Перайсці да наступнага"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Перайсці да папярэдняга"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Націсніце, каб перазапусціць гэту праграму і перайсці ў поўнаэкранны рэжым."</string>
- <string name="got_it" msgid="4428750913636945527">"Зразумела"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Праблемы з камерай?\nНацісніце, каб пераабсталяваць"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некаторыя праграмы лепш за ўсё працуюць у кніжнай арыентацыі"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Каб эфектыўна выкарыстоўваць прастору, паспрабуйце адзін з гэтых варыянтаў"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Каб перайсці ў поўнаэкранны рэжым, павярніце прыладу"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двойчы націсніце побач з праграмай, каб перамясціць яе"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index d33bf99e2ebd..3566bc372820 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
<string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Перамясціць PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Разгарнуць відарыс у відарысе"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Згарнуць відарыс у відарысе"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Двойчы націсніце "<annotation icon="home_icon">" ГАЛОЎНЫ ЭКРАН "</annotation>" для пераходу ў налады"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 80895dc0c032..c22fb86a4d4d 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затваряне"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Разгъване"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Преминаване към разделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Към следващия елемент"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Към предишния елемент"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Докоснете, за да рестартирате това приложение в режим на цял екран."</string>
- <string name="got_it" msgid="4428750913636945527">"Разбрах"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблеми с камерата?\nДокоснете за ремонтиране"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Някои приложения работят най-добре във вертикален режим"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Изпробвайте една от следните опции, за да се възползвате максимално от мястото на екрана"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Завъртете екрана си, за да преминете в режим на цял екран"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Докоснете два пъти дадено приложение, за да промените позицията му"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
index f4fad601179f..91049fd2cf02 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"„Картина в картина“: Преместв."</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Разгъване на прозореца за PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Свиване на прозореца за PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" За достъп до контролите натиснете 2 пъти "<annotation icon="home_icon">"НАЧАЛО"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index bdda799b001f..c0944e0584e6 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"বন্ধ করুন"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"বড় করুন"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"সেটিংস"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"\'স্প্লিট স্ক্রিন\' মোড চালু করুন"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"এগিয়ে যাওয়ার জন্য এড়িয়ে যান"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"পিছনে যাওয়ার জন্য এড়িয়ে যান"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string>
<string name="restart_button_description" msgid="5887656107651190519">"এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন ও \'ফুল-স্ক্রিন\' মোড ব্যবহার করুন।"</string>
- <string name="got_it" msgid="4428750913636945527">"বুঝেছি"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ক্যামেরা সংক্রান্ত সমস্যা?\nরিফিট করতে ট্যাপ করুন"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"কিছু অ্যাপ \'পোর্ট্রেট\' মোডে সবচেয়ে ভাল কাজ করে"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"আপনার স্পেস সবচেয়ে ভালভাবে কাজে লাগাতে এইসব বিকল্পের মধ্যে কোনও একটি ব্যবহার করে দেখুন"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"\'ফুল স্ক্রিন\' মোডে যেতে ডিভাইস ঘোরান"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"কোনও অ্যাপের পাশে ডবল ট্যাপ করে সেটির জায়গা পরিবর্তন করুন"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
index 0eb83a0276e6..792708d128a5 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP সরান"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP বড় করুন"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP আড়াল করুন"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" কন্ট্রোলের জন্য "<annotation icon="home_icon">" হোম "</annotation>" বোতামে ডবল প্রেস করুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 759e9b8c415b..ae01c641cc43 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvori podijeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je u načinu priakza Slika u slici"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da <xliff:g id="NAME">%s</xliff:g> koristi ovu funkciju, dodirnite da otvorite postavke i isključite je."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Preskoči na sljedeći"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Preskoči na prethodni"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da ponovo pokrenete ovu aplikaciju i aktivirate prikaz preko cijelog ekrana."</string>
- <string name="got_it" msgid="4428750913636945527">"Razumijem"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string>
+ <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="6688664582871779215">"Određene aplikacije najbolje funkcioniraju u uspravnom načinu rada"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da maksimalno iskoristite prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zarotirajte uređaj da aktivirate prikaz preko cijelog ekrana"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da promijenite njen položaj"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
index 8e301b0a8f4d..b7f0dca1b5a5 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
@@ -19,6 +19,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Pokreni sliku u slici"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Proširi sliku u slici"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Suzi sliku u slici"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">" POČETNI EKRAN "</annotation>" za kontrole"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 202ea2083b7e..8a522b3e6397 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -20,14 +20,17 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tanca"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Desplega"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuració"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entra al mode de pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en pantalla en pantalla"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si no vols que <xliff:g id="NAME">%s</xliff:g> utilitzi aquesta funció, toca per obrir la configuració i desactiva-la."</string>
<string name="pip_play" msgid="3496151081459417097">"Reprodueix"</string>
<string name="pip_pause" msgid="690688849510295232">"Posa en pausa"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Ves al següent"</string>
- <string name="pip_skip_to_prev" msgid="7172158111196394092">"Torna a l\'anterior"</string>
+ <string name="pip_skip_to_prev" msgid="7172158111196394092">"Ves a l\'anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toca per reiniciar aquesta aplicació i passar a pantalla completa."</string>
- <string name="got_it" msgid="4428750913636945527">"Entesos"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string>
+ <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="6688664582871779215">"Algunes aplicacions funcionen millor en posició vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una d\'aquestes opcions per treure el màxim profit de l\'espai"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositiu per passar a pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Fes doble toc al costat d\'una aplicació per canviar-ne la posició"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index b80fc41402dd..1c560c7afa06 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mou pantalla en pantalla"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Desplega pantalla en pantalla"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Replega pantalla en pantalla"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Prem dos cops "<annotation icon="home_icon">" INICI "</annotation>" per accedir als controls"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 08a4201b2fae..d0cf80aef38c 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zavřít"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozbalit"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavení"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivovat rozdělenou obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Nabídka"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikace <xliff:g id="NAME">%s</xliff:g> je v režimu obraz v obraze"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Pokud nechcete, aby aplikace <xliff:g id="NAME">%s</xliff:g> tuto funkci používala, klepnutím otevřete nastavení a funkci vypněte."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Přeskočit na další"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Přeskočit na předchozí"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Klepnutím aplikaci restartujete a přejdete na režim celé obrazovky"</string>
- <string name="got_it" msgid="4428750913636945527">"Rozumím"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string>
+ <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="6688664582871779215">"Některé aplikace fungují nejlépe na výšku"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pokud chcete maximálně využít prostor, vyzkoušejte jednu z těchto možností"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zařízení přejděte do režimu celé obrazovky"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedle aplikace změňte její umístění"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
index 56abcbe473fb..9a8cc2b4d70e 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Přesunout PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Rozbalit PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Sbalit PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Ovládací prvky zobrazíte dvojitým stisknutím "<annotation icon="home_icon">"tlačítka plochy"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 395f6e75cdbc..bb81c10c6e1b 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Luk"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Udvid"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Indstillinger"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Åbn opdelt skærm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> vises som integreret billede"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Hvis du ikke ønsker, at <xliff:g id="NAME">%s</xliff:g> skal benytte denne funktion, kan du åbne indstillingerne og deaktivere den."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Gå videre til næste"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Gå til forrige"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tryk for at genstarte denne app, og gå til fuld skærm."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string>
+ <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="6688664582871779215">"Nogle apps fungerer bedst i stående format"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv én af disse muligheder for at få mest muligt ud af dit rum"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Drej din enhed for at gå til fuld skærm"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryk to gange ud for en app for at ændre dens placering"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
index fdb6b783399e..cba660ac723c 100644
--- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string>
<string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Flyt PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Udvid PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Skjul PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Tryk to gange på "<annotation icon="home_icon">" HJEM "</annotation>" for at se indstillinger"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index ab3461a1ebf5..3a1107939c9f 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"„Bildschirm teilen“ aktivieren"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Wenn du nicht möchtest, dass <xliff:g id="NAME">%s</xliff:g> diese Funktion verwendet, tippe, um die Einstellungen zu öffnen und die Funktion zu deaktivieren."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Vorwärts springen"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Rückwärts springen"</string>
<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 bei geteiltem Bildschirmmodus 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="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
@@ -60,9 +63,9 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</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 die Bubble öffnen möchtest, tippe sie an. Wenn du sie verschieben möchtest, zieh an ihr."</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>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Bubble-Einstellungen festlegen"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tippe auf \"Verwalten\", um Bubbles für diese App zu deaktivieren"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tippe auf „Verwalten“, um Bubbles für diese App zu deaktivieren"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Keine kürzlich geschlossenen Bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Hier werden aktuelle und geschlossene Bubbles angezeigt"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tippe, um die App im Vollbildmodus neu zu starten."</string>
- <string name="got_it" msgid="4428750913636945527">"Ok"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string>
+ <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="6688664582871779215">"Einige Apps funktionieren am besten im Hochformat"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Mithilfe dieser Möglichkeiten kannst du dein Display optimal nutzen"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gerät drehen, um zum Vollbildmodus zu wechseln"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Neben einer App doppeltippen, um die Position zu ändern"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 02cce9d73647..02a1b66eb63f 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string>
+ <string name="pip_move" msgid="1544227837964635439">"BiB verschieben"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"BiB maximieren"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"BiB minimieren"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Für Steuerelemente zweimal "<annotation icon="home_icon">"STARTBILDSCHIRMTASTE"</annotation>" drücken"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 75e4379284b8..70f55058925c 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Κλείσιμο"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Ανάπτυξη"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ρυθμίσεις"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Μετάβαση σε διαχωρισμό οθόνης"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Μενού"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Η λειτουργία picture-in-picture είναι ενεργή σε <xliff:g id="NAME">%s</xliff:g>."</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Εάν δεν θέλετε να χρησιμοποιείται αυτή η λειτουργία από την εφαρμογή <xliff:g id="NAME">%s</xliff:g>, πατήστε για να ανοίξετε τις ρυθμίσεις και απενεργοποιήστε την."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Μετάβαση στο επόμενο"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Μετάβαση στο προηγούμενο"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Πατήστε για επανεκκίνηση αυτής της εφαρμογής και ενεργοποίηση πλήρους οθόνης."</string>
- <string name="got_it" msgid="4428750913636945527">"Το κατάλαβα"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nΠατήστε για επιδιόρθωση."</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Ορισμένες εφαρμογές λειτουργούν καλύτερα σε κατακόρυφο προσανατολισμό"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Δοκιμάστε μία από αυτές τις επιλογές για να αξιοποιήσετε στο έπακρο τον χώρο σας."</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Περιστρέψτε τη συσκευή σας για μετάβαση σε πλήρη οθόνη."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Πατήστε δύο φορές δίπλα σε μια εφαρμογή για να αλλάξετε τη θέση της."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
index 880ea37e6bf7..24cd030cd754 100644
--- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string>
<string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Μετακίνηση PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Ανάπτυξη PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Σύμπτυξη PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Πατήστε δύο φορές "<annotation icon="home_icon">" ΑΡΧΙΚΗ ΟΘΟΝΗ "</annotation>" για στοιχεία ελέγχου"</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 0d7b60ffefc6..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Skip to next"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Skip to previous"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
+ <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="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
index e3f08c8cc76f..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</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 0d7b60ffefc6..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Skip to next"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Skip to previous"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
+ <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="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index e3f08c8cc76f..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</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 0d7b60ffefc6..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Skip to next"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Skip to previous"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
+ <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="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
index e3f08c8cc76f..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</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 0d7b60ffefc6..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Close"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expand"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Skip to next"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Skip to previous"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
+ <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="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
index e3f08c8cc76f..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</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 4bff89d68963..5c3d0f65374a 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎Close‎‏‎‎‏‎"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎Expand‎‏‎‎‏‎"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎Settings‎‏‎‎‏‎"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎Enter split screen‎‏‎‎‏‎"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎Menu‎‏‎‎‏‎"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ is in picture-in-picture‎‏‎‎‏‎"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎If you don\'t want ‎‏‎‎‏‏‎<xliff:g id="NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ to use this feature, tap to open settings and turn it off.‎‏‎‎‏‎"</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎Skip to next‎‏‎‎‏‎"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎Skip to previous‎‏‎‎‏‎"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎Manage‎‏‎‎‏‎"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎Bubble dismissed.‎‏‎‎‏‎"</string>
<string name="restart_button_description" msgid="5887656107651190519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎Tap to restart this app and go full screen.‎‏‎‎‏‎"</string>
- <string name="got_it" msgid="4428750913636945527">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎Got it‎‏‎‎‏‎"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎Camera issues?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Tap to refit‎‏‎‎‏‎"</string>
+ <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="6688664582871779215">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎Some apps work best in portrait‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎Try one of these options to make the most of your space‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎Rotate your device to go full screen‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎Double-tap next to an app to reposition it‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
index 3f9ef0ea2816..a6e494cfed3c 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎(No title program)‎‏‎‎‏‎"</string>
<string name="pip_close" msgid="9135220303720555525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎Close PIP‎‏‎‎‏‎"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎Full screen‎‏‎‎‏‎"</string>
+ <string name="pip_move" msgid="1544227837964635439">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‎Move PIP‎‏‎‎‏‎"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎Expand PIP‎‏‎‎‏‎"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‏‎‎Collapse PIP‎‏‎‎‏‎"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‎‏‎ Double press ‎‏‎‎‏‏‎"<annotation icon="home_icon">"‎‏‎‎‏‏‏‎ HOME ‎‏‎‎‏‏‎"</annotation>"‎‏‎‎‏‏‏‎ for controls‎‏‎‎‏‎"</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 90c4d51b995a..e523ae53b0cc 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Cerrar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en modo de Pantalla en pantalla"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si no quieres que <xliff:g id="NAME">%s</xliff:g> use esta función, presiona para abrir la configuración y desactivarla."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Siguiente"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
@@ -58,7 +61,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_conversation" msgid="310000317885712693">"No mostrar la conversación en 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>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controla las burbujas"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Presiona para reiniciar esta app y acceder al modo de pantalla completa."</string>
- <string name="got_it" msgid="4428750913636945527">"Entendido"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string>
+ <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="6688664582871779215">"Algunas apps funcionan mejor en modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba estas opciones para aprovechar al máximo tu espacio"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rota el dispositivo para ver la pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Presiona dos veces junto a una app para cambiar su posición"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
index 5d5954a19761..458f6b15b857 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Maximizar PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Minimizar PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Presiona dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 2207e62e9503..974960708190 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Cerrar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Mostrar"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ajustes"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en imagen en imagen"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si no quieres que <xliff:g id="NAME">%s</xliff:g> utilice esta función, toca la notificación para abrir los ajustes y desactivarla."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Saltar al siguiente"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Volver al anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
@@ -60,7 +63,7 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</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 llamadas \"burbujas\". Toca para abrir la burbuja. Arrastra para moverla."</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamadas \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controla las burbujas"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toca Gestionar para desactivar las burbujas de esta aplicación"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toca para reiniciar esta aplicación e ir a la pantalla completa."</string>
- <string name="got_it" msgid="4428750913636945527">"Entendido"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string>
+ <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="6688664582871779215">"Algunas aplicaciones funcionan mejor en vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba una de estas opciones para sacar el máximo partido al espacio de tu pantalla"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositivo para ir al modo de pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dos veces junto a una aplicación para cambiar su posición"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index d31b9b45cae3..0a690984dac5 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover imagen en imagen"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Mostrar imagen en imagen"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Ocultar imagen en imagen"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 9222a91b044c..a5f82a6452c4 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sule"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Laiendamine"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Seaded"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ava jagatud ekraanikuva"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menüü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on režiimis Pilt pildis"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Kui te ei soovi, et rakendus <xliff:g id="NAME">%s</xliff:g> seda funktsiooni kasutaks, puudutage seadete avamiseks ja lülitage see välja."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Järgmise juurde"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Eelmise juurde"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Puudutage rakenduse taaskäivitamiseks ja täisekraanrežiimi aktiveerimiseks."</string>
- <string name="got_it" msgid="4428750913636945527">"Selge"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string>
+ <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="6688664582871779215">"Mõni rakendus töötab kõige paremini vertikaalpaigutuses"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proovige ühte neist valikutest, et oma ruumi parimal moel kasutada"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pöörake seadet, et aktiveerida täisekraanirežiim"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Topeltpuudutage rakenduse kõrval, et selle asendit muuta"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
index bc7a6adafc03..dc0232303a70 100644
--- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string>
<string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Teisalda PIP-režiimi"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Laienda PIP-akent"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Ahenda PIP-aken"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Nuppude nägemiseks vajutage 2 korda nuppu "<annotation icon="home_icon">"AVAKUVA"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 4a59b59df27b..caa335a96222 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Itxi"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Zabaldu"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ezarpenak"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Sartu pantaila zatituan"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menua"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Pantaila txiki gainjarrian dago <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ez baduzu nahi <xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Joan hurrengora"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Joan aurrekora"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Saka ezazu aplikazioa berrabiarazteko, eta ezarri pantaila osoko modua."</string>
- <string name="got_it" msgid="4428750913636945527">"Ados"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string>
+ <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="6688664582871779215">"Aplikazio batzuk orientazio bertikalean funtzionatzen dute hobekien"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pantailako eremuari ahalik eta etekinik handiena ateratzeko, probatu aukera hauetakoren bat"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pantaila osoko modua erabiltzeko, biratu gailua"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren ondoko edozein toki"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
index cf5f98883082..bce06da2c66f 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string>
<string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mugitu pantaila txiki gainjarria"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Zabaldu pantaila txiki gainjarria"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Tolestu pantaila txiki gainjarria"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Kontrolatzeko aukerak atzitzeko, sakatu birritan "<annotation icon="home_icon">" HASIERA "</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index a17f543278f7..9e7257112c6c 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"بستن"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"بزرگ کردن"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"تنظیمات"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ورود به حالت «صفحهٔ دونیمه»"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"منو"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"رد شدن به بعدی"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"رد شدن به قبلی"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
@@ -43,7 +46,7 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"٪۵۰ بالا"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"٪۳۰ بالا"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"تمام‌صفحه پایین"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از «حالت تک حرکت»"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یک‌دستی"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحه‌نمایش تند به‌طرف بالا بکشید یا در هر جایی از بالای برنامه که می‌خواهید ضربه بزنید"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت تک حرکت»"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت تک حرکت»"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string>
<string name="restart_button_description" msgid="5887656107651190519">"برای بازراه‌اندازی این برنامه و تغییر به حالت تمام‌صفحه، ضربه بزنید."</string>
- <string name="got_it" msgid="4428750913636945527">"متوجه‌ام"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"برخی‌از برنامه‌ها در حالت عمودی عملکرد بهتری دارند"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"با امتحان کردن یکی از این گزینه‌ها، بیشترین بهره را از فضایتان ببرید"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"برای رفتن به حالت تمام صفحه، دستگاهتان را بچرخانید"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"در کنار برنامه دوضربه بزنید تا جابه‌جا شود"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index 5b815b4c7b86..ff9a03c6cefb 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string>
<string name="pip_close" msgid="9135220303720555525">"‏بستن PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string>
+ <string name="pip_move" msgid="1544227837964635439">"‏انتقال PIP (تصویر در تصویر)"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"گسترده کردن «تصویر در تصویر»"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"جمع کردن «تصویر در تصویر»"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" برای کنترل‌ها، دکمه "<annotation icon="home_icon">"صفحه اصلی"</annotation>" را دوبار فشار دهید"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 332dc9b14da2..c809b4879e71 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sulje"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Laajenna"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Asetukset"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Avaa jaettu näyttö"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valikko"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on kuva kuvassa ‑tilassa"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Jos et halua, että <xliff:g id="NAME">%s</xliff:g> voi käyttää tätä ominaisuutta, avaa asetukset napauttamalla ja poista se käytöstä."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Siirry seuraavaan"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Siirry edelliseen"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Napauta, niin sovellus käynnistyy uudelleen ja siirtyy koko näytön tilaan."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string>
+ <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="6688664582871779215">"Osa sovelluksista toimii parhaiten pystytilassa"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Kokeile jotakin näistä vaihtoehdoista, jotta saat parhaan hyödyn näytön tilasta"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Käännä laitetta, niin se siirtyy koko näytön tilaan"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kaksoisnapauta sovellusta, jos haluat siirtää sitä"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
index 77ad6eef91e7..3e8bf9032780 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string>
<string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Siirrä PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Laajenna PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Tiivistä PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Asetukset: paina "<annotation icon="home_icon">"ALOITUSNÄYTTÖPAINIKETTA"</annotation>" kahdesti"</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 f51fc66b6b83..62b2bb65a603 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fermer"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Développer"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entrer dans l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode d\'incrustation d\'image"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si vous ne voulez pas que <xliff:g id="NAME">%s</xliff:g> utilise cette fonctionnalité, touchez l\'écran pour ouvrir les paramètres, puis désactivez-la."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Passer au suivant"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Revenir au précédent"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Touchez pour redémarrer cette application et passer en plein écran."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Certaines applications fonctionnent mieux en mode portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour tirer le meilleur parti de votre espace"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter votre appareil pour passer en plein écran"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
index 0ec7f40f0e9f..66e13b89c64b 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Déplacer l\'image incrustée"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Développer l\'image incrustée"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Réduire l\'image incrustée"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Appuyez deux fois sur "<annotation icon="home_icon">" ACCUEIL "</annotation>" pour les commandes"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 8fa06e88cd50..b3e22af0a3e3 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fermer"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Développer"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accéder à l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode Picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si vous ne voulez pas que l\'application <xliff:g id="NAME">%s</xliff:g> utilise cette fonctionnalité, appuyez ici pour ouvrir les paramètres et la désactiver."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Passer au contenu suivant"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Passer au contenu précédent"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
@@ -61,7 +64,7 @@
<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 bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Contrôler les paramètres des bulles"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Contrôlez les bulles à tout moment"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Appuyez sur \"Gérer\" pour désactiver les bulles de cette application"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Appuyez pour redémarrer cette application et activer le mode plein écran."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string>
+ <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="6688664582871779215">"Certaines applis fonctionnent mieux en mode Portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour exploiter pleinement l\'espace"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter l\'appareil pour passer en plein écran"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Appuyez deux fois à côté d\'une appli pour la repositionner"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
index 27fd155535b7..ed9baf5b6215 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Déplacer le PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Développer la fenêtre PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Réduire la fenêtre PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Menu de commandes : appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 56188d4e92ba..b8e039602243 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Pechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Despregar"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Inserir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está na pantalla superposta"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Se non queres que <xliff:g id="NAME">%s</xliff:g> utilice esta función, toca a configuración para abrir as opcións e desactivar a función."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Ir ao seguinte"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Ir ao anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toca o botón para reiniciar esta aplicación e abrila en pantalla completa."</string>
- <string name="got_it" msgid="4428750913636945527">"Entendido"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string>
+ <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="6688664582871779215">"Algunhas aplicacións funcionan mellor en modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proba unha destas opcións para sacar o máximo proveito do espazo da pantalla"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xira o dispositivo para ver o contido en pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dúas veces a carón dunha aplicación para cambiala de posición"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
index df96f6cb794d..a057434d7853 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover pantalla superposta"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Despregar pantalla superposta"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Contraer pantalla superposta"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Preme "<annotation icon="home_icon">"INICIO"</annotation>" dúas veces para acceder aos controis"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index b76e91010dcc..deda2d755e20 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"બંધ કરો"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"વિસ્તૃત કરો"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"સેટિંગ"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"વિભાજિત સ્ક્રીન મોડમાં દાખલ થાઓ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"મેનૂ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"આગલા પર જાઓ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"પહેલાંના પર જાઓ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
@@ -59,7 +62,7 @@
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> સેટિંગ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોડી દો"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"વાતચીતને બબલ કરશો નહીં"</string>
- <string name="bubbles_user_education_title" msgid="2112319053732691899">"બબલનો ઉપયોગ કરીને ચેટ કરો"</string>
+ <string name="bubbles_user_education_title" msgid="2112319053732691899">"બબલનો ઉપયોગ કરીને ચૅટ કરો"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"નવી વાતચીત ફ્લોટિંગ આઇકન અથવા બબલ જેવી દેખાશે. બબલને ખોલવા માટે ટૅપ કરો. તેને ખસેડવા માટે ખેંચો."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"બબલને કોઈપણ સમયે નિયંત્રિત કરો"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"આ ઍપમાંથી બબલને બંધ કરવા માટે મેનેજ કરો પર ટૅપ કરો"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string>
<string name="restart_button_description" msgid="5887656107651190519">"આ ઍપ ફરીથી ચાલુ કરવા માટે ટૅપ કરીને પૂર્ણ સ્ક્રીન કરો."</string>
- <string name="got_it" msgid="4428750913636945527">"સમજાઈ ગયું"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં સમસ્યાઓ છે?\nફરીથી ફિટ કરવા માટે ટૅપ કરો"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"અમુક ઍપ પોર્ટ્રેટ મોડમાં શ્રેષ્ઠ રીતે કાર્ય કરે છે"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"તમારી સ્પેસનો વધુને વધુ લાભ લેવા માટે, આ વિકલ્પોમાંથી કોઈ એક અજમાવો"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"પૂર્ણ સ્ક્રીન મોડ લાગુ કરવા માટે, તમારા ડિવાઇસને ફેરવો"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બાજુમાં બે વાર ટૅપ કરો"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
index 3608f1d530c0..d9525910e4c6 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP ખસેડો"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP મોટી કરો"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP નાની કરો"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" નિયંત્રણો માટે "<annotation icon="home_icon">" હોમ "</annotation>" બટન પર બે વાર દબાવો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index b2c005544c32..36b11514c7e5 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बंद करें"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तार करें"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन मोड में जाएं"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेन्यू"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"अगले पर जाएं"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"पिछले पर जाएं"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string>
<string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string>
- <string name="got_it" msgid="4428750913636945527">"ठीक है"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्या कैमरे से जुड़ी कोई समस्या है?\nफिर से फ़िट करने के लिए टैप करें"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"कुछ ऐप्लिकेशन, पोर्ट्रेट मोड में सबसे अच्छी तरह काम करते हैं"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"जगह का पूरा इस्तेमाल करने के लिए, इनमें से किसी एक विकल्प को आज़माएं"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फ़ुल स्क्रीन मोड में जाने के लिए, डिवाइस को घुमाएं"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बगल में दो बार टैप करें"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index 720bb6ca5e24..d897ac73f80d 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्‍क्रीन"</string>
+ <string name="pip_move" msgid="1544227837964635439">"पीआईपी को दूसरी जगह लेकर जाएं"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"पीआईपी विंडो को बड़ा करें"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"पीआईपी विंडो को छोटा करें"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" कंट्रोल मेन्यू पर जाने के लिए, "<annotation icon="home_icon">" होम बटन"</annotation>" दो बार दबाएं"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 769d1d26aed2..5ecc5585a6e9 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zatvori"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Proširivanje"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvorite podijeljeni zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izbornik"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> jest na slici u slici"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da aplikacija <xliff:g id="NAME">%s</xliff:g> upotrebljava tu značajku, dodirnite da biste otvorili postavke i isključili je."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Preskoči na sljedeće"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Preskoči na prethodno"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste ponovo pokrenuli tu aplikaciju i prikazali je na cijelom zaslonu."</string>
- <string name="got_it" msgid="4428750913636945527">"Shvaćam"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string>
+ <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="6688664582871779215">"Neke aplikacije najbolje funkcioniraju u portretnom usmjerenju"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste maksimalno iskoristili prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zakrenite uređaj radi prikaza na cijelom zaslonu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste joj promijenili položaj"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
index 21f8cb63f470..8f5f3164c4d7 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Premjesti PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Proširi PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Sažmi PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">"POČETNI ZASLON"</annotation>" za kontrole"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 05655f1bb607..2295250e2853 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Bezárás"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Kibontás"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Beállítások"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Váltás osztott képernyőre"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A(z) <xliff:g id="NAME">%s</xliff:g> kép a képben funkciót használ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ha nem szeretné, hogy a(z) <xliff:g id="NAME">%s</xliff:g> használja ezt a funkciót, koppintson a beállítások megnyitásához, és kapcsolja ki."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Ugrás a következőre"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Ugrás az előzőre"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Koppintson az alkalmazás újraindításához és a teljes képernyős mód elindításához."</string>
- <string name="got_it" msgid="4428750913636945527">"Rendben"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string>
+ <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="6688664582871779215">"Egyes alkalmazások álló tájolásban működnek a leghatékonyabban"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Próbálja ki az alábbi beállítások egyikét, hogy a legjobban ki tudja használni képernyő területét"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"A teljes képernyős mód elindításához forgassa el az eszközt"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Koppintson duplán az alkalmazás mellett az áthelyezéséhez"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
index 0010086bb0b5..fc8d79589121 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP áthelyezése"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Kép a képben kibontása"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Kép a képben összecsukása"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Vezérlők: "<annotation icon="home_icon">" KEZDŐKÉPERNYŐ "</annotation>" gomb kétszer megnyomva"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 5f7495e63613..208936539094 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Փակել"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Ընդարձակել"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Կարգավորումներ"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Մտնել տրոհված էկրանի ռեժիմ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ընտրացանկ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Անցնել հաջորդին"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Վերադառնալ նախորդին"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string>
<string name="restart_button_description" msgid="5887656107651190519">"Հպեք՝ հավելվածը վերագործարկելու և լիաէկրան ռեժիմին անցնելու համար։"</string>
- <string name="got_it" msgid="4428750913636945527">"Եղավ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Որոշ հավելվածներ լավագույնս աշխատում են դիմանկարի ռեժիմում"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Փորձեք այս տարբերակներից մեկը՝ տարածքը հնարավորինս արդյունավետ օգտագործելու համար"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Պտտեք սարքը՝ լիաէկրան ռեժիմին անցնելու համար"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
index cb18762be48b..f5665b8dd166 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string>
<string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Տեղափոխել PIP-ը"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Ծավալել PIP-ը"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Ծալել PIP-ը"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Կարգավորումների համար կրկնակի սեղմեք "<annotation icon="home_icon">"ԳԼԽԱՎՈՐ ԷԿՐԱՆ"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 2cf50c0644f1..1b46b2fe2570 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tutup"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Luaskan"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setelan"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk ke mode layar terpisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> adalah picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Jika Anda tidak ingin <xliff:g id="NAME">%s</xliff:g> menggunakan fitur ini, ketuk untuk membuka setelan dan menonaktifkannya."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Lewati ke berikutnya"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Lewati ke sebelumnya"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Ketuk untuk memulai ulang aplikasi ini dan membuka layar penuh."</string>
- <string name="got_it" msgid="4428750913636945527">"Oke"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string>
+ <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="6688664582871779215">"Beberapa aplikasi berfungsi paling baik dalam mode potret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Coba salah satu opsi berikut untuk mengoptimalkan area layar Anda"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar perangkat untuk tampilan layar penuh"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketuk dua kali di samping aplikasi untuk mengubah posisinya"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
index 8f3a28764b00..a1535653f679 100644
--- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Pindahkan PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Luaskan PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Ciutkan PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Tekan dua kali "<annotation icon="home_icon">" HOME "</annotation>" untuk membuka kontrol"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 7a3b6a693e7a..a201c95137f3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Loka"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Stækka"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Stillingar"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Opna skjáskiptingu"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valmynd"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er með mynd í mynd"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ef þú vilt ekki að <xliff:g id="NAME">%s</xliff:g> noti þennan eiginleika skaltu ýta til að opna stillingarnar og slökkva á því."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Fara á næsta"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Fara á fyrra"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Ýttu til að endurræsa forritið og sýna það á öllum skjánum."</string>
- <string name="got_it" msgid="4428750913636945527">"Ég skil"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string>
+ <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="6688664582871779215">"Sum forrit virka best í skammsniði"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prófaðu einhvern af eftirfarandi valkostum til að nýta plássið sem best"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Snúðu tækinu til að nota allan skjáinn"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ýttu tvisvar við hlið forritsins til að færa það"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
index 1f148d948a0e..70ca1afe3aea 100644
--- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string>
<string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Færa innfellda mynd"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Stækka innfellda mynd"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Minnka innfellda mynd"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Ýttu tvisvar á "<annotation icon="home_icon">" HEIM "</annotation>" til að opna stillingar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index b061d1f592c4..dd5416f2e398 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Chiudi"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Espandi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Impostazioni"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accedi a schermo diviso"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> è in Picture in picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Se non desideri che l\'app <xliff:g id="NAME">%s</xliff:g> utilizzi questa funzione, tocca per aprire le impostazioni e disattivarla."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Passa ai contenuti successivi"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Passa ai contenuti precedenti"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
@@ -60,7 +63,7 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora bolla"</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 visualizzate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</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>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlla le bolle quando vuoi"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tocca Gestisci per disattivare le bolle dall\'app"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tocca per riavviare l\'app e passare alla modalità a schermo intero."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string>
+ <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="6688664582871779215">"Alcune app funzionano in modo ottimale in verticale"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una di queste opzioni per ottimizzare lo spazio a tua disposizione"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ruota il dispositivo per passare alla modalità a schermo intero"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tocca due volte accanto a un\'app per riposizionarla"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
index 127454cf28bf..cda627517872 100644
--- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string>
<string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Sposta PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Espandi PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Comprimi PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Premi due volte "<annotation icon="home_icon">" HOME "</annotation>" per aprire i controlli"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 4b4310256766..52a6b0676222 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"סגירה"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"הרחבה"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"כניסה למסך המפוצל"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"אפשר לדלג אל הבא"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"אפשר לדלג אל הקודם"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string>
<string name="restart_button_description" msgid="5887656107651190519">"צריך להקיש כדי להפעיל מחדש את האפליקציה הזו ולעבור למסך מלא."</string>
- <string name="got_it" msgid="4428750913636945527">"הבנתי"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"חלק מהאפליקציות פועלות בצורה הטובה ביותר במצב תצוגה לאורך"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"כדי להפיק את המרב משטח המסך, ניתן לנסות את אחת מהאפשרויות האלה"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"מסובבים את המכשיר כדי לעבור לתצוגה במסך מלא"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"מקישים הקשה כפולה ליד אפליקציה כדי למקם אותה מחדש"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index ef98a9c41cf2..30ce97b998ca 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
<string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string>
+ <string name="pip_move" msgid="1544227837964635439">"‏העברת תמונה בתוך תמונה (PIP)"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"הרחבת חלון תמונה-בתוך-תמונה"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"כיווץ של חלון תמונה-בתוך-תמונה"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" לחיצה כפולה על "<annotation icon="home_icon">" הלחצן הראשי "</annotation>" תציג את אמצעי הבקרה"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index ab693d2e5045..5a25c24ba034 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"閉じる"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"分割画面に切り替え"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"メニュー"</string>
<string name="pip_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>でこの機能を使用しない場合は、タップして設定を開いて OFF にしてください。"</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"次へスキップ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"前へスキップ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
@@ -61,7 +64,7 @@
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"会話をバブルで表示しない"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"チャットでバブルを使う"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"新しい会話はフローティング アイコン(バブル)として表示されます。タップするとバブルが開きます。ドラッグしてバブルを移動できます。"</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"いつでもバブルを管理"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"バブルはいつでも管理可能"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"このアプリからのバブルを OFF にするには、[管理] をタップしてください"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string>
<string name="restart_button_description" msgid="5887656107651190519">"タップしてこのアプリを再起動すると、全画面表示になります。"</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"アプリによっては縦向きにすると正常に動作します"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"スペースを最大限に活用するには、以下の方法のいずれかをお試しください"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"全画面表示にするにはデバイスを回転させてください"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"位置を変えるにはアプリの横をダブルタップしてください"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
index b7ab28c44fd2..e58e7bf6fabc 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP を移動"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP を開く"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP を閉じる"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" コントロールにアクセス: "<annotation icon="home_icon">" ホーム "</annotation>" を 2 回押します"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index ef9a84f4ce2f..bff86fa6ffe2 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"დახურვა"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"გაშლა"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"პარამეტრები"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"გაყოფილ ეკრანში შესვლა"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"მენიუ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"შემდეგზე გადასვლა"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"წინაზე გადასვლა"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string>
<string name="restart_button_description" msgid="5887656107651190519">"შეეხეთ ამ აპის გადასატვირთად და გადადით სრულ ეკრანზე."</string>
- <string name="got_it" msgid="4428750913636945527">"გასაგებია"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"კამერად პრობლემები აქვს?\nშეეხეთ გამოსასწორებლად"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ზოგიერთი აპი უკეთ მუშაობს პორტრეტის რეჟიმში"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"გამოცადეთ ამ ვარიანტებიდან ერთ-ერთი, რათა მაქსიმალურად ისარგებლოთ თქვენი მეხსიერებით"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"მოატრიალეთ თქვენი მოწყობილობა სრული ეკრანის გასაშლელად"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ორმაგად შეეხეთ აპის გვერდითა სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
index 1bf4b8ebdcda..b09686646c8b 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP გადატანა"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP-ის გაშლა"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP-ის ჩაკეცვა"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" მართვის საშუალებებზე წვდომისთვის ორმაგად დააჭირეთ "<annotation icon="home_icon">" მთავარ ღილაკს "</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 13f3a4eed8c5..f57f3f581c85 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Жабу"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Жаю"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Бөлінген экранға кіру"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Mәзір"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Келесіге өту"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Алдыңғысына оралу"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
@@ -68,7 +71,14 @@
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Соңғы және жабылған қалқыма хабарлар осы жерде көрсетіледі."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string>
- <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқымалы анықтама өшірілді."</string>
+ <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Бұл қолданбаны қайта қосып, толық экранға өту үшін түртіңіз."</string>
- <string name="got_it" msgid="4428750913636945527">"Түсінікті"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Кейбір қолданба портреттік режимде жақсы жұмыс істейді"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Экранды тиімді пайдалану үшін мына опциялардың бірін байқап көріңіз."</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толық экранға ауысу үшін құрылғыңызды бұрыңыз."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Қолданбаның орнын ауыстыру үшін жанынан екі рет түртіңіз."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
index 8f1e725e79e2..7bade0dff0d9 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP клипін жылжыту"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP терезесін жаю"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP терезесін жию"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Басқару элементтері: "<annotation icon="home_icon">" НЕГІЗГІ ЭКРАН "</annotation>" түймесін екі рет басыңыз."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 134d3c2334f0..5c04f881fe0e 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"បិទ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ពង្រីក"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ការកំណត់"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ចូលមុខងារ​បំបែកអេក្រង់"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ម៉ឺនុយ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"រំលងទៅបន្ទាប់"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"រំលងទៅក្រោយ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោល​សារលេចឡើង។"</string>
<string name="restart_button_description" msgid="5887656107651190519">"ចុចដើម្បី​ចាប់ផ្ដើម​កម្មវិធី​នេះឡើងវិញ រួចចូលប្រើ​ពេញអេក្រង់។"</string>
- <string name="got_it" msgid="4428750913636945527">"យល់ហើយ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"មានបញ្ហា​ពាក់ព័ន្ធនឹង​កាមេរ៉ាឬ?\nចុចដើម្បី​ដោះស្រាយ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបាន​ដោះស្រាយ​បញ្ហានេះទេឬ?\nចុចដើម្បី​ត្រឡប់"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមាន​បញ្ហាពាក់ព័ន្ធនឹង​កាមេរ៉ាទេឬ? ចុចដើម្បី​ច្រានចោល។"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"កម្មវិធីមួយចំនួនដំណើរការបានប្រសើរបំផុតក្នុងទិសដៅបញ្ឈរ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"សាកល្បងជម្រើសមួយក្នុងចំណោមទាំងនេះ ដើម្បីទទួលបានអត្ថប្រយោជន៍ច្រើនបំផុតពីកន្លែងទំនេររបស់អ្នក"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"បង្វិលឧបករណ៍របស់អ្នក ដើម្បីចូលប្រើអេក្រង់ពេញ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ចុចពីរដងនៅជាប់កម្មវិធីណាមួយ ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
index b55997056e66..721be1fc1650 100644
--- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធី​គ្មានចំណងជើង)"</string>
<string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string>
+ <string name="pip_move" msgid="1544227837964635439">"ផ្លាស់ទី PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"ពង្រីក PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"បង្រួម PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ចុចពីរដងលើ"<annotation icon="home_icon">"ប៊ូតុងដើម"</annotation>" ដើម្បីបើកផ្ទាំងគ្រប់គ្រង"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index c8b3389a3a60..e91383caa009 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ಮುಚ್ಚಿ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ವಿಸ್ತೃತಗೊಳಿಸು"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ಸ್ಪ್ಲಿಟ್‌-ಸ್ಕ್ರೀನ್‌ಗೆ ಪ್ರವೇಶಿಸಿ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ಮುಂದಕ್ಕೆ ಸ್ಕಿಪ್‌ ಮಾಡಿ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ಹಿಂದಕ್ಕೆ ಸ್ಕಿಪ್‌ ಮಾಡಿ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
<string name="restart_button_description" msgid="5887656107651190519">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
- <string name="got_it" msgid="4428750913636945527">"ಅರ್ಥವಾಯಿತು"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿವೆಯೇ?\nಮರುಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ಕೆಲವು ಆ್ಯಪ್‌ಗಳು ಪೋರ್ಟ್ರೇಟ್ ಮೋಡ್‌ನಲ್ಲಿ ಅತ್ಯುತ್ತಮವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ನಿಮ್ಮ ಸ್ಥಳಾವಕಾಶದ ಅತಿಹೆಚ್ಚು ಪ್ರಯೋಜನ ಪಡೆಯಲು ಈ ಆಯ್ಕೆಗಳಲ್ಲಿ ಒಂದನ್ನು ಬಳಸಿ ನೋಡಿ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಪಕ್ಕದಲ್ಲಿ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 9d3942fa4dd3..8310c8a1169c 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP ಅನ್ನು ಸರಿಸಿ"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ವಿಸ್ತರಿಸಿ"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ಕುಗ್ಗಿಸಿ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ಕಂಟ್ರೋಲ್‌ಗಳಿಗಾಗಿ "<annotation icon="home_icon">" ಹೋಮ್ "</annotation>" ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index b29612e337f2..104ba3f22c96 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"닫기"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"펼치기"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"설정"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"화면 분할 모드로 전환"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"메뉴"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>에서 PIP 사용 중"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>에서 이 기능이 사용되는 것을 원하지 않는 경우 탭하여 설정을 열고 기능을 사용 중지하세요."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"다음으로 건너뛰기"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"이전으로 건너뛰기"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string>
<string name="restart_button_description" msgid="5887656107651190519">"탭하여 이 앱을 다시 시작하고 전체 화면으로 이동합니다."</string>
- <string name="got_it" msgid="4428750913636945527">"확인"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"일부 앱은 세로 모드에서 가장 잘 작동함"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"공간을 최대한 이용할 수 있도록 이 옵션 중 하나를 시도해 보세요."</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"전체 화면 모드로 전환하려면 기기를 회전하세요."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"앱 위치를 조정하려면 앱 옆을 두 번 탭하세요."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
index 46d6ad4e0b0f..a3e055a515a1 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP 이동"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP 펼치기"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP 접기"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" 제어 메뉴에 액세스하려면 "<annotation icon="home_icon">" 홈 "</annotation>"을 두 번 누르세요."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 0c64c7639327..8203622a33fc 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Жабуу"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Жайып көрсөтүү"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Жөндөөлөр"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Экранды бөлүү режимине өтүү"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Кийинкисине өткөрүп жиберүү"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Мурункусуна өткөрүп жиберүү"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
@@ -44,7 +47,7 @@
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Үстүнкү экранды 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ылдыйкы экранды толук экран режимине өткөрүү"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бир кол режимин колдонуу"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө көздөй сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Бир кол режиминен чыгуу"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер жөндөөлөрү"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Бул колдонмону өчүрүп күйгүзүп, толук экранга өтүү үчүн таптап коюңуз."</string>
- <string name="got_it" msgid="4428750913636945527">"Түшүндүм"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада маселелер келип чыктыбы?\nОңдоо үчүн таптаңыз"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Айрым колдонмолорду тигинен иштетүү туура болот"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Иш чөйрөсүнүн бардык мүмкүнчүлүктөрүн пайдалануу үчүн бул параметрлердин бирин колдонуп көрүңүз"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толук экран режимине өтүү үчүн түзмөктү буруңуз"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Колдонмонун ракурсун өзгөртүү үчүн анын тушуна эки жолу басыңыз"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
index d5d1d7ef914e..887ac52c8e43 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP\'ти жылдыруу"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP\'ти жайып көрсөтүү"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP\'ти жыйыштыруу"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Башкаруу элементтерин ачуу үчүн "<annotation icon="home_icon">" БАШКЫ БЕТ "</annotation>" баскычын эки жолу басыңыз"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
index 0ed9368aa067..e89f65bef792 100644
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ b/libs/WindowManager/Shell/res/values-land/styles.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="DockedDividerBackground">
- <item name="android:layout_width">10dp</item>
+ <item name="android:layout_width">@dimen/split_divider_bar_width</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:background">@color/split_divider_background</item>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 5ccf164b3d67..24396786f9d8 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ປິດ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ຂະຫຍາຍ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ການຕັ້ງຄ່າ"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ເຂົ້າການແບ່ງໜ້າຈໍ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ເມນູ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ຂ້າມໄປລາຍການໜ້າ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ຂ້າມໄປລາຍການກ່ອນນີ້"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string>
<string name="restart_button_description" msgid="5887656107651190519">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ ແລະ ໃຊ້ແບບເຕັມຈໍ."</string>
- <string name="got_it" msgid="4428750913636945527">"ເຂົ້າໃຈແລ້ວ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ?\nແຕະເພື່ອປັບໃໝ່"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອ​ປິດ​ໄວ້."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ແອັບບາງຢ່າງເຮັດວຽກໄດ້ດີທີ່ສຸດໃນໂໝດລວງຕັ້ງ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ໃຫ້ລອງຕົວເລືອກໃດໜຶ່ງເຫຼົ່ານີ້ເພື່ອໃຊ້ປະໂຫຍດຈາກພື້ນທີ່ຂອງທ່ານໃຫ້ໄດ້ສູງສຸດ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ໝຸນອຸປະກອນຂອງທ່ານເພື່ອໃຊ້ແບບເຕັມຈໍ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ແຕະສອງເທື່ອໃສ່ຖັດຈາກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
index f6362c120b9f..91c4a033356d 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string>
<string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string>
+ <string name="pip_move" msgid="1544227837964635439">"ຍ້າຍ PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"ຂະຫຍາຍ PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"ຫຍໍ້ PIP ລົງ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ກົດ "<annotation icon="home_icon">" HOME "</annotation>" ສອງເທື່ອສຳລັບການຄວບຄຸມ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 1433312ded60..e2ae643ad308 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Uždaryti"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Išskleisti"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nustatymai"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Įjungti išskaidyto ekrano režimą"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> rodom. vaizdo vaizde"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Jei nenorite, kad „<xliff:g id="NAME">%s</xliff:g>“ naudotų šią funkciją, palietę atidarykite nustatymus ir išjunkite ją."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Praleisti ir eiti į kitą"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Praleisti ir eiti į ankstesnį"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Palieskite, kad paleistumėte iš naujo šią programą ir įjungtumėte viso ekrano režimą."</string>
- <string name="got_it" msgid="4428750913636945527">"Supratau"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string>
+ <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="6688664582871779215">"Kai kurios programos geriausiai veikia stačiuoju režimu"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pabandykite naudoti vieną iš šių parinkčių, kad išnaudotumėte visą vietą"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pasukite įrenginį, kad įjungtumėte viso ekrano režimą"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dukart palieskite šalia programos, kad pakeistumėte jos poziciją"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
index e4695a05f038..04265ca01b48 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string>
<string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Perkelti PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Iškleisti PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Sutraukti PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Jei reikia valdiklių, dukart paspauskite "<annotation icon="home_icon">"PAGRINDINIS"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index fb297b8f2990..a77160bc262a 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Aizvērt"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Izvērst"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Iestatījumi"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Piekļūt ekrāna sadalīšanas režīmam"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izvēlne"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ir attēlā attēlā"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ja nevēlaties lietotnē <xliff:g id="NAME">%s</xliff:g> izmantot šo funkciju, pieskarieties, lai atvērtu iestatījumus un izslēgtu funkciju."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Pāriet uz nākamo"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Pāriet uz iepriekšējo"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Pieskarieties, lai restartētu šo lietotni un pārietu pilnekrāna režīmā."</string>
- <string name="got_it" msgid="4428750913636945527">"Labi"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string>
+ <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="6688664582871779215">"Dažas lietotnes vislabāk darbojas portreta režīmā"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Izmēģiniet vienu no šīm iespējām, lai efektīvi izmantotu pieejamo vietu"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pagrieziet ierīci, lai aktivizētu pilnekrāna režīmu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Veiciet dubultskārienu blakus lietotnei, lai manītu tās pozīciju"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
index f2b037fbeeee..8c6191e00833 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string>
<string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Pārvietot attēlu attēlā"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Izvērst “Attēls attēlā” logu"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Sakļaut “Attēls attēlā” logu"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Atvērt vadīklas: divreiz nospiediet pogu "<annotation icon="home_icon">"SĀKUMS"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 80b33293af34..bac0c9eee4c2 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затвори"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Проширете"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Поставки"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Влези во поделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Прескокни до следната"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Прескокни до претходната"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Допрете за да ја рестартирате апликацијава и да ја отворите на цел екран."</string>
- <string name="got_it" msgid="4428750913636945527">"Сфатив"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некои апликации најдобро работат во режим на портрет"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Испробајте една од опцииве за да го извлечете максимумот од вашиот простор"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте го уредот за да отворите на цел екран"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Допрете двапати до некоја апликација за да ја преместите"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
index 25dc764f4d5e..beef1fef862b 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Премести PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Прошири ја сликата во слика"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Собери ја сликата во слика"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Притиснете двапати на "<annotation icon="home_icon">" HOME "</annotation>" за контроли"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 5d47911137ec..de0f837fcd3f 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"അവസാനിപ്പിക്കുക"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"വികസിപ്പിക്കുക"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ക്രമീകരണം"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"സ്ക്രീൻ വിഭജന മോഡിൽ പ്രവേശിക്കുക"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"മെനു"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"അടുത്തതിലേക്ക് പോകുക"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"മുമ്പത്തേതിലേക്ക് പോകുക"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
@@ -66,9 +69,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"മനസ്സിലായി"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"അടുത്തിടെയുള്ള ബബിളുകൾ ഒന്നുമില്ല"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"അടുത്തിടെയുള്ള ബബിളുകൾ, ഡിസ്മിസ് ചെയ്ത ബബിളുകൾ എന്നിവ ഇവിടെ ദൃശ്യമാവും"</string>
- <string name="notification_bubble_title" msgid="6082910224488253378">"ബബ്ൾ"</string>
+ <string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string>
- <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബ്ൾ ഡിസ്മിസ് ചെയ്തു."</string>
+ <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string>
<string name="restart_button_description" msgid="5887656107651190519">"ഈ ആപ്പ് റീസ്‌റ്റാർട്ട് ചെയ്‌ത് പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ടാപ്പ് ചെയ്യുക."</string>
- <string name="got_it" msgid="4428750913636945527">"മനസ്സിലായി"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ക്യാമറ പ്രശ്നങ്ങളുണ്ടോ?\nശരിയാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ചില ആപ്പുകൾ പോർട്രെയ്റ്റിൽ മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്നു"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"നിങ്ങളുടെ ഇടം പരമാവധി പ്രയോജനപ്പെടുത്താൻ ഈ ഓപ്ഷനുകളിലൊന്ന് പരീക്ഷിക്കുക"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ഈ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ഒരു ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ, അതിന് തൊട്ടടുത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index c74e0bbfaa5b..c2a532d09647 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്‍ണ്ണ സ്ക്രീന്‍"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP നീക്കുക"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP വികസിപ്പിക്കുക"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP ചുരുക്കുക"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" നിയന്ത്രണങ്ങൾക്കായി "<annotation icon="home_icon">" ഹോം "</annotation>" രണ്ട് തവണ അമർത്തുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index a5e7f957b829..1205306e0833 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Хаах"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Дэлгэх"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Тохиргоо"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Хуваасан дэлгэцийг оруулна уу"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Цэс"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Дараагийн медиад очих"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Өмнөх медиад очих"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Энэ аппыг дахин эхлүүлж, бүтэн дэлгэцэд орохын тулд товшино уу."</string>
- <string name="got_it" msgid="4428750913636945527">"Ойлголоо"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерын асуудал гарсан уу?\nДахин тааруулахын тулд товшино уу"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Зарим апп нь босоо чиглэлд хамгийн сайн ажилладаг"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Орон зайгаа сайтар ашиглахын тулд эдгээр сонголтуудын аль нэгийг туршиж үзээрэй"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Төхөөрөмжөө бүтэн дэлгэцээр үзэхийн тулд эргүүлнэ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Аппыг дахин байрлуулахын тулд хажууд нь хоёр товшино"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
index 55519d462b69..bf8c59b57359 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP-г зөөх"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP-г дэлгэх"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP-г хураах"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Хяналтад хандах бол "<annotation icon="home_icon">" HOME "</annotation>" дээр хоёр дарна уу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 0450189c515d..c91d06fdf3d5 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बंद करा"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तृत करा"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग्ज"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन एंटर करा"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनू"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"डावलून पुढे जा"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"डावलून मागे जा"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string>
<string name="restart_button_description" msgid="5887656107651190519">"हे अ‍ॅप रीस्टार्ट करण्यासाठी आणि फुल स्क्रीन करण्यासाठी टॅप करा."</string>
- <string name="got_it" msgid="4428750913636945527">"समजले"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"कॅमेराशी संबंधित काही समस्या आहेत का?\nपुन्हा फिट करण्यासाठी टॅप करा"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्‍यासाठी टॅप करा."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"काही ॲप्स पोर्ट्रेटमध्ये सर्वोत्तम काम करतात"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"तुमच्या स्पेसचा पुरेपूर वापर करण्यासाठी, यांपैकी एक पर्याय वापरून पहा"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फुल स्क्रीन करण्यासाठी, तुमचे डिव्हाइस फिरवा"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या शेजारी दोनदा टॅप करा"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index ad2cfc6035c2..5d519b7afe9a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP हलवा"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP चा विस्तार करा"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP कोलॅप्स करा"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" नियंत्रणांसाठी "<annotation icon="home_icon">" होम "</annotation>" दोनदा दाबा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index c0c1cbd6ed3c..652a9919d163 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Tutup"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Kembangkan"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Tetapan"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk skrin pisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> terdapat dalam gambar dalam gambar"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Jika anda tidak mahu <xliff:g id="NAME">%s</xliff:g> menggunakan ciri ini, ketik untuk membuka tetapan dan matikan ciri."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Langkau ke seterusnya"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Langkau ke sebelumnya"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Ketik untuk memulakan semula apl ini dan menggunakan skrin penuh."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string>
+ <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="6688664582871779215">"Sesetengah apl berfungsi paling baik dalam mod potret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Cuba salah satu daripada pilihan ini untuk memanfaatkan ruang anda sepenuhnya"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar peranti anda untuk beralih ke skrin penuh"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketik dua kali bersebelahan apl untuk menempatkan semula apl"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
index b2d7214381ef..08642c47c91a 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Alihkan PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Kembangkan PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Kuncupkan PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Tekan dua kali "<annotation icon="home_icon">" LAMAN UTAMA "</annotation>" untuk mengakses kawalan"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 0d78f89d9e54..15d182c6451e 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -20,14 +20,17 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ပိတ်ရန်"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ချဲ့ရန်"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ဆက်တင်များ"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသို့ ဝင်ရန်"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"မီနူး"</string>
- <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> သည် တစ်ခုပေါ် တစ်ခုထပ်၍ ဖွင့်ထားသည်"</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>
<string name="pip_pause" msgid="690688849510295232">"ခေတ္တရပ်ရန်"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"နောက်တစ်ခုသို့ ကျော်ရန်"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ယခင်တစ်ခုသို့ ပြန်သွားရန်"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string>
<string name="restart_button_description" msgid="5887656107651190519">"ဤအက်ပ်ကို ပြန်စပြီး ဖန်သားပြင်အပြည့်လုပ်ရန် တို့ပါ။"</string>
- <string name="got_it" msgid="4428750913636945527">"ရပြီ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ကင်မရာပြဿနာလား။\nပြင်ဆင်ရန် တို့ပါ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"အချို့အက်ပ်များသည် ဒေါင်လိုက်တွင် အကောင်းဆုံးလုပ်ဆောင်သည်"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"သင့်နေရာကို အကောင်းဆုံးအသုံးပြုနိုင်ရန် ဤရွေးစရာများထဲမှ တစ်ခုကို စမ်းကြည့်ပါ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ဖန်သားပြင်အပြည့်လုပ်ရန် သင့်စက်ကို လှည့်နိုင်သည်"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"အက်ပ်နေရာပြန်ချရန် ၎င်းဘေးတွင် နှစ်ချက်တို့နိုင်သည်"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
index 9569dc4cbeea..e01daee115ca 100644
--- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
@@ -17,8 +17,12 @@
<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="notification_channel_tv_pip" msgid="2576686079160402435">"နှစ်ခုထပ်၍ကြည့်ခြင်း"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP ရွှေ့ရန်"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP ကို ချဲ့ရန်"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP ကို လျှော့ပြပါ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ထိန်းချုပ်မှုအတွက် "<annotation icon="home_icon">" ပင်မခလုတ် "</annotation>" နှစ်ချက်နှိပ်ပါ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index fab0c0cacef4..9fd42b2f129c 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Lukk"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Vis"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Innstillinger"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivér delt skjerm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er i bilde-i-bilde"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Hvis du ikke vil at <xliff:g id="NAME">%s</xliff:g> skal bruke denne funksjonen, kan du trykke for å åpne innstillingene og slå den av."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Hopp til neste"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Hopp til forrige"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Trykk for å starte denne appen på nytt og vise den i fullskjerm."</string>
- <string name="got_it" msgid="4428750913636945527">"Greit"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string>
+ <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="6688664582871779215">"Noen apper fungerer best i stående format"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv et av disse alternativene for å få mest mulig ut av plassen din"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Roter enheten for å starte fullskjerm"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dobbelttrykk ved siden av en app for å flytte den"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
index 8a7f315606ad..65ed0b7f5bff 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string>
<string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Flytt BIB"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Vis BIB"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Skjul BIB"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Dobbelttrykk på "<annotation icon="home_icon">"HJEM"</annotation>" for å åpne kontroller"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index dfa364a763a8..8dfec88cc29d 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"बन्द गर्नुहोस्"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"विस्तृत गर्नुहोस्"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिङहरू"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रिन मोड प्रयोग गर्नुहोस्"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनु"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> Picture-in-picture मा छ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"तपाईं <xliff:g id="NAME">%s</xliff:g> ले सुविधा प्रयोग नगरोस् भन्ने चाहनुहुन्छ भने ट्याप गरेर सेटिङहरू खोल्नुहोस् र यसलाई निष्क्रिय पार्नुहोस्।"</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"अर्कोमा जानुहोस्"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"अघिल्लोमा जानुहोस्"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string>
<string name="restart_button_description" msgid="5887656107651190519">"यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस् र फुल स्क्रिन मोडमा जानुहोस्।"</string>
- <string name="got_it" msgid="4428750913636945527">"बुझेँ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्यामेरासम्बन्धी समस्या देखियो?\nसमस्या हल गर्न ट्याप गर्नुहोस्"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"केही एपहरूले पोर्ट्रेटमा राम्रोसँग काम गर्छन्"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"तपाईं स्क्रिनको अधिकतम ठाउँ प्रयोग गर्न चाहनुहुन्छ भने यीमध्ये कुनै विकल्प प्रयोग गरी हेर्नुहोस्"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"तपाईं फुल स्क्रिन मोड हेर्न चाहनुहुन्छ भने आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको छेउमा डबल ट्याप गर्नुहोस्"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
index 87fa3279f05e..d33fed67efb6 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP सार्नुहोस्"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP विन्डो एक्स्पान्ड गर्नु…"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP विन्डो कोल्याप्स गर्नुहोस्"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" कन्ट्रोल मेनु खोल्न "<annotation icon="home_icon">" होम "</annotation>" बटन दुई पटक थिच्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 0601f153b330..8468b04c66da 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Sluiten"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Uitvouwen"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellingen"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gesplitst scherm openen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in scherm-in-scherm"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Als je niet wilt dat <xliff:g id="NAME">%s</xliff:g> deze functie gebruikt, tik je om de instellingen te openen en zet je de functie uit."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Doorgaan naar volgende"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Teruggaan naar vorige"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
@@ -43,10 +46,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bovenste scherm 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Onderste scherm op volledig scherm"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bediening met één hand gebruiken"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bediening met 1 hand gebruiken"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bediening met één hand starten"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Bediening met één hand afsluiten"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bediening met 1 hand starten"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Bediening met 1 hand afsluiten"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Instellingen voor <xliff:g id="APP_NAME">%1$s</xliff:g>-bubbels"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Overloop"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Weer toevoegen aan stack"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tik om deze app opnieuw te starten en te openen op het volledige scherm."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string>
+ <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="6688664582871779215">"Sommige apps werken het best in de staande stand"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van deze opties om optimaal gebruik te maken van je ruimte"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai je apparaat om naar volledig scherm te schakelen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
index df3809e5d6c6..9763c5665ab2 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string>
+ <string name="pip_move" msgid="1544227837964635439">"SIS verplaatsen"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"SIS uitvouwen"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"SIS samenvouwen"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Druk twee keer op "<annotation icon="home_icon">" HOME "</annotation>" voor bedieningselementen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 50d20076a26f..a8d8448edf99 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ବଢ଼ାନ୍ତୁ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ସେଟିଂସ୍"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ମୋଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ମେନୁ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ପରବର୍ତ୍ତୀକୁ ଯାଆନ୍ତୁ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ପୂର୍ବବର୍ତ୍ତୀକୁ ଛାଡ଼ନ୍ତୁ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string>
<string name="restart_button_description" msgid="5887656107651190519">"ଏହି ଆପକୁ ରିଷ୍ଟାର୍ଟ କରି ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
- <string name="got_it" msgid="4428750913636945527">"ବୁଝିଗଲି"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"କ୍ୟାମେରାରେ ସମସ୍ୟା ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"କିଛି ଆପ ପୋର୍ଟ୍ରେଟରେ ସବୁଠାରୁ ଭଲ କାମ କରେ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ଆପଣଙ୍କ ସ୍ପେସରୁ ଅଧିକ ଲାଭ ପାଇବାକୁ ଏହି ବିକଳ୍ପଗୁଡ଼ିକ ମଧ୍ୟରୁ ଗୋଟିଏ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ପୂର୍ଣ୍ଣ-ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହା ପାଖରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
index 295a5c4ee1ce..e0344855bd1f 100644
--- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍‍ ପ୍ରୋଗ୍ରାମ୍‍ ନାହିଁ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIPକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIPକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIPକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ପାଇଁ "<annotation icon="home_icon">" ହୋମ ବଟନ "</annotation>"କୁ ଦୁଇଥର ଦବାନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index dd3d26e56b4c..f99176cb682d 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ਬੰਦ ਕਰੋ"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ਮੀਨੂ"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ਅਗਲੇ \'ਤੇ ਜਾਓ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ਪਿਛਲੇ \'ਤੇ ਜਾਓ"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string>
<string name="restart_button_description" msgid="5887656107651190519">"ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਪੂਰੀ ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਓ।"</string>
- <string name="got_it" msgid="4428750913636945527">"ਸਮਝ ਲਿਆ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ਕੁਝ ਐਪਾਂ ਪੋਰਟਰੇਟ ਵਿੱਚ ਬਿਹਤਰ ਕੰਮ ਕਰਦੀਆਂ ਹਨ"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ਆਪਣੀ ਜਗ੍ਹਾ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਲਾਹਾ ਲੈਣ ਲਈ ਇਨ੍ਹਾਂ ਵਿਕਲਪਾਂ ਵਿੱਚੋਂ ਕੋਈ ਇੱਕ ਵਰਤ ਕੇ ਦੇਖੋ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁਮਾਓ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਅੱਗੇ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
index e32895a9a239..9c01ac3f3cc0 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP ਨੂੰ ਲਿਜਾਓ"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP ਨੂੰ ਸਮੇਟੋ"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" ਕੰਟਰੋਲਾਂ ਲਈ "<annotation icon="home_icon">" ਹੋਮ ਬਟਨ "</annotation>" ਨੂੰ ਦੋ ਵਾਰ ਦਬਾਓ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index ca2bdcb270da..f2147c04d335 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zamknij"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozwiń"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ustawienia"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Włącz podzielony ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikacja <xliff:g id="NAME">%s</xliff:g> działa w trybie obraz w obrazie"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Jeśli nie chcesz, by aplikacja <xliff:g id="NAME">%s</xliff:g> korzystała z tej funkcji, otwórz ustawienia i wyłącz ją."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Dalej"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Wstecz"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string>
<string name="restart_button_description" msgid="5887656107651190519">"Kliknij, by uruchomić tę aplikację ponownie i przejść w tryb pełnoekranowy."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string>
+ <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="6688664582871779215">"Niektóre aplikacje działają najlepiej w orientacji pionowej"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Wypróbuj jedną z tych opcji, aby jak najlepiej wykorzystać miejsce"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Obróć urządzenie, aby przejść do pełnego ekranu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kliknij dwukrotnie obok aplikacji, aby ją przenieść"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
index 286fd7b2ff0f..b922e2d5a6ba 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Przenieś PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Rozwiń PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Zwiń PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Naciśnij dwukrotnie "<annotation icon="home_icon">"EKRAN GŁÓWNY"</annotation>", aby wyświetlić ustawienia"</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 bdd0d4b8baec..2efc5543dd87 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -18,8 +18,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
- <string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
+ <string name="pip_phone_expand" msgid="2579292903468287504">"Abrir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Se você não quer que o app <xliff:g id="NAME">%s</xliff:g> use este recurso, toque para abrir as configurações e desativá-lo."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Pular para a próxima"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Pular para a anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string>
- <string name="got_it" msgid="4428750913636945527">"Ok"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
+ <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="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
index 57edcdf74cf4..cc4eb3c32c1f 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Abrir picture-in-picture"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Fechar picture-in-picture"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Pressione o botão "<annotation icon="home_icon">"home"</annotation>" duas vezes para acessar os controles"</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 6661b0506c5e..c68a6934dead 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Definições"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aceder ao ecrã dividido"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> está no modo de ecrã no ecrã"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Se não pretende que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Mudar para o seguinte"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Mudar para o anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar esta app e ficar em ecrã inteiro."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string>
+ <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="6688664582871779215">"Algumas apps funcionam melhor no modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Experimente uma destas opções para aproveitar ao máximo o seu espaço"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rode o dispositivo para ficar em ecrã inteiro"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes junto a uma app para a reposicionar"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
index 9372e0f637cb..c4ae78d89ba8 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover Ecrã no ecrã"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expandir Ecrã no ecrã"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Reduzir Ecrã no ecrã"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Prima duas vezes "<annotation icon="home_icon">" PÁGINA INICIAL "</annotation>" para controlos"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index bdd0d4b8baec..2efc5543dd87 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -18,8 +18,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pip_phone_close" msgid="5783752637260411309">"Fechar"</string>
- <string name="pip_phone_expand" msgid="2579292903468287504">"Expandir"</string>
+ <string name="pip_phone_expand" msgid="2579292903468287504">"Abrir"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Se você não quer que o app <xliff:g id="NAME">%s</xliff:g> use este recurso, toque para abrir as configurações e desativá-lo."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Pular para a próxima"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Pular para a anterior"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string>
- <string name="got_it" msgid="4428750913636945527">"Ok"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
+ <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="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
index 57edcdf74cf4..cc4eb3c32c1f 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Abrir picture-in-picture"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Fechar picture-in-picture"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Pressione o botão "<annotation icon="home_icon">"home"</annotation>" duas vezes para acessar os controles"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 9112543c8f60..804d34f980ff 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Închideți"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Treceți la următorul"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Treceți la cel anterior"</string>
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionați"</string>
+ <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stocați"</string>
+ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulați 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="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Atingeți ca să reporniți aplicația și să treceți în modul ecran complet."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Aveți probleme cu camera foto?\nAtingeți pentru a reîncadra"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ați remediat problema?\nAtingeți pentru a reveni"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu aveți probleme cu camera foto? Atingeți pentru a închide."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Unele aplicații funcționează cel mai bine în orientarea portret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Încercați una dintre aceste opțiuni pentru a profita din plin de spațiu"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotiți dispozitivul pentru a trece în modul ecran complet"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Atingeți de două ori lângă o aplicație pentru a o repoziționa"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index 9438e4955b68..86a30f49df15 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Mutați fereastra PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Extindeți fereastra PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Restrângeți fereastra PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Apăsați de două ori "<annotation icon="home_icon">"butonul ecran de pornire"</annotation>" pentru comenzi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 5120136e3c68..95bf1cf11435 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрыть"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Развернуть"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Включить разделение экрана"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Перейти к следующему"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Перейти к предыдущему"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Нажмите, чтобы перезапустить приложение и перейти в полноэкранный режим."</string>
- <string name="got_it" msgid="4428750913636945527">"ОК"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблемы с камерой?\nНажмите, чтобы исправить."</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некоторые приложения лучше работают в вертикальном режиме"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Чтобы эффективно использовать экранное пространство, выполните одно из следующих действий:"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Чтобы перейти в полноэкранный режим, поверните устройство."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Чтобы переместить приложение, нажмите на него дважды."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
index 24785aa7e184..08623e1e69c5 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string>
<string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Переместить PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Развернуть PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Свернуть PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Элементы управления: дважды нажмите "<annotation icon="home_icon">" кнопку главного экрана "</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index e1d9a825a004..23dd65ad7b31 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"වසන්න"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"දිග හරින්න"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"සැකසීම්"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"බෙදුම් තිරයට ඇතුළු වන්න"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"මෙනුව"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ඊළඟ එකට පනින්න"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"පෙර එකට පනින්න"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string>
<string name="restart_button_description" msgid="5887656107651190519">"මෙම යෙදුම යළි ඇරඹීමට සහ පූර්ණ තිරයට යාමට තට්ටු කරන්න."</string>
- <string name="got_it" msgid="4428750913636945527">"තේරුණා"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"කැමරා ගැටලුද?\nයළි සවි කිරීමට තට්ටු කරන්න"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්‍රතිවර්තනය කිරීමට තට්ටු කරන්න"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"සමහර යෙදුම් ප්‍රතිමූර්තිය තුළ හොඳින්ම ක්‍රියා කරයි"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ඔබගේ ඉඩෙන් උපරිම ප්‍රයෝජන ගැනීමට මෙම විකල්පවලින් එකක් උත්සාහ කරන්න"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"සම්පූර්ණ තිරයට යාමට ඔබගේ උපාංගය කරකවන්න"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"එය නැවත ස්ථානගත කිරීමට යෙදුමකට යාබදව දෙවරක් තට්ටු කරන්න"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
index 62ee6d4f44d2..fbb0ebba0623 100644
--- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP ගෙන යන්න"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP දිග හරින්න"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP හකුළන්න"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" පාලන සඳහා "<annotation icon="home_icon">" මුල් පිටුව "</annotation>" දෙවරක් ඔබන්න"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index c88099b35c0d..a231cacefb20 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zavrieť"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Rozbaliť"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavenia"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Prejsť na rozdelenú obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ponuka"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v režime obraz v obraze"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ak nechcete, aby aplikácia <xliff:g id="NAME">%s</xliff:g> používala túto funkciu, klepnutím otvorte nastavenia a vypnite ju."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Preskočiť na ďalšie"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Preskočiť na predchádzajúce"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Klepnutím reštartujete túto aplikáciu a prejdete do režimu celej obrazovky."</string>
- <string name="got_it" msgid="4428750913636945527">"Dobre"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string>
+ <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="6688664582871779215">"Niektoré aplikácie fungujú najlepšie v režime na výšku"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Vyskúšajte jednu z týchto možností a využívajte svoj priestor naplno"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zariadenia prejdete do režimu celej obrazovky"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedľa aplikácie zmeníte jej pozíciu"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
index a7a515cdc61c..81cb0eafc759 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Presunúť obraz v obraze"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Rozbaliť obraz v obraze"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Zbaliť obraz v obraze"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Ovládanie zobraz. dvoj. stlač. "<annotation icon="home_icon">" TLAČIDLA PLOCHY "</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 42d7be7a146d..adeaae978eaa 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Zapri"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Razširi"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavitve"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Vklopi razdeljen zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v načinu slika v sliki"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Če ne želite, da aplikacija <xliff:g id="NAME">%s</xliff:g> uporablja to funkcijo, se dotaknite, da odprete nastavitve, in funkcijo izklopite."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Preskoči na naslednjega"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Preskoči na prejšnjega"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Dotaknite se za vnovični zagon te aplikacije in preklop v celozaslonski način."</string>
- <string name="got_it" msgid="4428750913636945527">"Razumem"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string>
+ <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="6688664582871779215">"Nekatere aplikacije najbolje delujejo v navpični postavitvi"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Poskusite eno od teh možnosti za čim boljši izkoristek prostora"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Če želite preklopiti v celozaslonski način, zasukajte napravo."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvakrat se dotaknite ob aplikaciji, če jo želite prestaviti."</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
index fe5c9ae5d2a8..060aaa0ce647 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Premakni sliko v sliki"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Razširi sliko v sliki"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Strni sliko v sliki"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Za kontrolnike dvakrat pritisnite gumb za "<annotation icon="home_icon">" ZAČETNI ZASLON "</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 1f373b53d0a4..2839b4bae7e4 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Mbyll"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Zgjero"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Cilësimet"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Hyr në ekranin e ndarë"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyja"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> është në figurë brenda figurës"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Nëse nuk dëshiron që <xliff:g id="NAME">%s</xliff:g> ta përdorë këtë funksion, trokit për të hapur cilësimet dhe për ta çaktivizuar."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Kalo te tjetra"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Kalo tek e mëparshmja"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Trokit për ta rinisur këtë aplikacion dhe për të kaluar në ekranin e plotë."</string>
- <string name="got_it" msgid="4428750913636945527">"E kuptova"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string>
+ <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="6688664582871779215">"Disa aplikacione funksionojnë më mirë në modalitetin vertikal"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Provo një nga këto opsione për ta shfrytëzuar sa më mirë hapësirën"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rrotullo ekranin për të kaluar në ekran të plotë"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Trokit dy herë pranë një aplikacioni për ta ripozicionuar"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
index 1d5583b2c826..9bfdb6a3edd8 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string>
<string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Zhvendos PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Zgjero PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Palos PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Trokit dy herë "<annotation icon="home_icon">" KREU "</annotation>" për kontrollet"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 2bbbbf9e0714..9db6b7c63610 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Затвори"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Прошири"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Подешавања"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Уђи на подељени екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Пређи на следеће"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Пређи на претходно"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Додирните да бисте рестартовали апликацију и прешли у режим целог екрана."</string>
- <string name="got_it" msgid="4428750913636945527">"Важи"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблема са камером?\nДодирните да бисте поново уклопили"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Неке апликације најбоље функционишу у усправном режиму"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Испробајте једну од ових опција да бисте на најбољи начин искористили простор"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте уређај за приказ преко целог екрана"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двапут додирните поред апликације да бисте променили њену позицију"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
index 62ad1e8f6e69..6bc5c87bab48 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Премести слику у слици"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Прошири слику у слици"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Скупи слику у слици"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Двапут притисните "<annotation icon="home_icon">" HOME "</annotation>" за контроле"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 692b5ed8576f..f6bd55423cdc 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Stäng"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Utöka"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Inställningar"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Starta delad skärm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> visas i bild-i-bild"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Om du inte vill att den här funktionen används i <xliff:g id="NAME">%s</xliff:g> öppnar du inställningarna genom att trycka. Sedan inaktiverar du funktionen."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Hoppa till nästa"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Hoppa till föregående"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Tryck för att starta om appen i helskärmsläge."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string>
+ <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="6688664582871779215">"Vissa appar fungerar bäst i stående läge"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Testa med ett av dessa alternativ för att få ut mest möjliga av ytan"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotera skärmen för att gå över till helskärmsläge"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryck snabbt två gånger bredvid en app för att flytta den"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
index 74fb590c3e4d..b3465ab1db85 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Flytta BIB"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Utöka bild-i-bild"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Komprimera bild-i-bild"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Tryck snabbt två gånger på "<annotation icon="home_icon">" HEM "</annotation>" för kontroller"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 61c95ee183a0..f6e558527ee5 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Funga"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Panua"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Mipangilio"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Weka skrini iliyogawanywa"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> iko katika hali ya picha ndani ya picha nyingine"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> itumie kipengele hiki, gusa ili ufungue mipangilio na uizime."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Ruka ufikie inayofuata"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Ruka ufikie iliyotangulia"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Gusa ili uzime na uwashe programu hii, kisha nenda kwenye skrini nzima."</string>
- <string name="got_it" msgid="4428750913636945527">"Nimeelewa"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string>
+ <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="6688664582871779215">"Baadhi ya programu hufanya kazi vizuri zaidi zikiwa wima"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Jaribu moja kati ya chaguo hizi ili utumie nafasi ya skrini yako kwa ufanisi"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungusha kifaa chako ili uende kwenye hali ya skrini nzima"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Gusa mara mbili karibu na programu ili uihamishe"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
index cf0d8a9b3910..baff49ed821a 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string>
<string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Kuhamisha PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Panua PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Kunja PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Bonyeza mara mbili kitufe cha "<annotation icon="home_icon">" UKURASA WA KWANZA "</annotation>" kupata vidhibiti"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 32a925a7994e..d8334adfe5ef 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"மூடு"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"விரி"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"அமைப்புகள்"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"திரைப் பிரிப்பு பயன்முறைக்குச் செல்"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"மெனு"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"அடுத்ததற்குச் செல்"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"முந்தையதற்குச் செல்"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string>
<string name="restart_button_description" msgid="5887656107651190519">"தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கலாம், முழுத்திரையில் பார்க்கலாம்."</string>
- <string name="got_it" msgid="4428750913636945527">"சரி"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"சில ஆப்ஸ் \'போர்ட்ரெய்ட்டில்\' சிறப்பாகச் செயல்படும்"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ஸ்பேஸ்களிலிருந்து அதிகப் பலன்களைப் பெற இந்த விருப்பங்களில் ஒன்றைப் பயன்படுத்துங்கள்"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"முழுத்திரைக்குச் செல்ல உங்கள் சாதனத்தைச் சுழற்றவும்"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ஆப்ஸை இடம் மாற்ற, ஆப்ஸுக்கு அடுத்து இருமுறை தட்டவும்"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
index 8bca46314e30..4439e299c919 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIPபை நகர்த்து"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIPபை விரிவாக்கு"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIPபைச் சுருக்கு"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" கட்டுப்பாடுகள்: "<annotation icon="home_icon">" முகப்பு "</annotation>" பட்டனை இருமுறை அழுத்துக"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 3db12e7d5661..733075550007 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"మూసివేయి"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"విస్తరింపజేయి"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"సెట్టింగ్‌లు"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"స్ప్లిట్ స్క్రీన్‌ను ఎంటర్ చేయండి"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"మెనూ"</string>
<string name="pip_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>
@@ -28,21 +29,23 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"దాటవేసి తర్వాత దానికి వెళ్లు"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"దాటవేసి మునుపటి దానికి వెళ్లు"</string>
<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_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</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="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</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>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్‌"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్‌"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్‌"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్‌ను ఉపయోగించడం"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్‌ను ప్రారంభిస్తుంది"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string>
<string name="restart_button_description" msgid="5887656107651190519">"ఈ యాప్‌ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేసి, ఆపై పూర్తి స్క్రీన్‌లోకి వెళ్లండి."</string>
- <string name="got_it" msgid="4428750913636945527">"అర్థమైంది"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా సమస్యలు ఉన్నాయా?\nరీఫిట్ చేయడానికి ట్యాప్ చేయండి"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"కొన్ని యాప్‌లు పోర్ట్రెయిట్‌లో ఉత్తమంగా పని చేస్తాయి"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"మీ ప్రదేశాన్ని ఎక్కువగా ఉపయోగించుకోవడానికి ఈ ఆప్షన్‌లలో ఒకదాన్ని ట్రై చేయండి"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ఫుల్ స్క్రీన్‌కు వెళ్లడానికి మీ పరికరాన్ని తిప్పండి"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"యాప్ స్థానాన్ని మార్చడానికి దాని పక్కన డబుల్-ట్యాప్ చేయండి"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
index 47489efbc4c2..35579346615f 100644
--- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
@@ -20,5 +20,9 @@
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string>
- <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string>
+ <string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్‌"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIPను తరలించండి"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIPని విస్తరించండి"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIPని కుదించండి"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" కంట్రోల్స్ కోసం "<annotation icon="home_icon">" HOME "</annotation>" బటన్ రెండుసార్లు నొక్కండి"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
new file mode 100644
index 000000000000..86ca65526336
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -0,0 +1,46 @@
+<?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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for TV products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The percentage of the screen width to use for the default width or height of
+ picture-in-picture windows. Regardless of the percent set here, calculated size will never
+ be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+ <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.2</item>
+
+ <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
+ 24x24
+ </string>
+
+ <!-- The default gravity for the picture-in-picture window.
+ Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x55</integer>
+
+ <!-- Fraction of screen width/height restricted keep clear areas can move the PiP. -->
+ <fraction name="config_pipMaxRestrictedMoveDistance">15%</fraction>
+
+ <!-- Duration (in milliseconds) the PiP stays stashed before automatically unstashing. -->
+ <integer name="config_pipStashDuration">5000</integer>
+
+ <!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
+ if a custom action is present before closing it. -->
+ <integer name="config_pipForceCloseDelay">5000</integer>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
new file mode 100644
index 000000000000..14e89f8b08df
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for TV products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Padding between PIP and keep clear areas that caused it to move. -->
+ <dimen name="pip_keep_clear_area_padding">16dp</dimen>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 7df76e84cbb7..cfee8ea3242e 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"ปิด"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"ขยาย"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"การตั้งค่า"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"เข้าสู่โหมดแบ่งหน้าจอ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"เมนู"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"ข้ามไปรายการถัดไป"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ข้ามไปรายการก่อนหน้า"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string>
<string name="restart_button_description" msgid="5887656107651190519">"แตะเพื่อรีสตาร์ทแอปนี้และแสดงแบบเต็มหน้าจอ"</string>
- <string name="got_it" msgid="4428750913636945527">"รับทราบ"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"หากพบปัญหากับกล้อง\nแตะเพื่อแก้ไข"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"บางแอปทำงานได้ดีที่สุดในแนวตั้ง"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ลองใช้หนึ่งในตัวเลือกเหล่านี้เพื่อให้ได้ประโยชน์สูงสุดจากพื้นที่ว่าง"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"หมุนอุปกรณ์ให้แสดงเต็มหน้าจอ"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"แตะสองครั้งข้างแอปเพื่อเปลี่ยนตำแหน่ง"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
index d3797e7c3cde..0a07d157ec6f 100644
--- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string>
<string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string>
+ <string name="pip_move" msgid="1544227837964635439">"ย้าย PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"ขยาย PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"ยุบ PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" กดปุ่ม "<annotation icon="home_icon">" หน้าแรก "</annotation>" สองครั้งเพื่อเปิดการควบคุม"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index d6c2784f764f..eed624dd5069 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Isara"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Palawakin"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Mga Setting"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Pumasok sa split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Nasa picture-in-picture ang <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Kung ayaw mong magamit ni <xliff:g id="NAME">%s</xliff:g> ang feature na ito, i-tap upang buksan ang mga setting at i-off ito."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Lumaktaw sa susunod"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Lumaktaw sa nakaraan"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string>
<string name="restart_button_description" msgid="5887656107651190519">"I-tap para i-restart ang app na ito at mag-full screen."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string>
+ <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="6688664582871779215">"May ilang app na pinakamainam gamitin nang naka-portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Subukan ang isa sa mga opsyong ito para masulit ang iyong space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"I-rotate ang iyong device para mag-full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Mag-double tap sa tabi ng isang app para iposisyon ito ulit"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
index b01c1115cd34..9a11a38fa492 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Ilipat ang PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"I-expand ang PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"I-collapse ang PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" I-double press ang "<annotation icon="home_icon">" HOME "</annotation>" para sa mga kontrol"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 47d5966549ef..2b4a2d0550f0 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Kapat"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Genişlet"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana geç"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>, pencere içinde pencere özelliğini kullanıyor"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> uygulamasının bu özelliği kullanmasını istemiyorsanız dokunarak ayarları açın ve söz konusu özelliği kapatın."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Sonrakine atla"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Öncekine atla"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Bu uygulamayı yeniden başlatmak ve tam ekrana geçmek için dokunun."</string>
- <string name="got_it" msgid="4428750913636945527">"Anladım"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string>
+ <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="6688664582871779215">"Bazı uygulamalar dikey modda en iyi performansı gösterir"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Alanınızı en verimli şekilde kullanmak için bu seçeneklerden birini deneyin"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana geçmek için cihazınızı döndürün"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Yeniden konumlandırmak için uygulamanın yanına iki kez dokunun"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
index c92c4d02f465..bf4bc6f1fff7 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIP\'yi taşı"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP penceresini genişlet"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP penceresini daralt"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Kontroller için "<annotation icon="home_icon">" ANA SAYFA "</annotation>"\'ya iki kez basın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 7920fd237a08..558ec51752b9 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -16,7 +16,19 @@
-->
<resources>
<!-- The dimensions to user for picture-in-picture action buttons. -->
- <dimen name="picture_in_picture_button_width">100dp</dimen>
- <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
+ <dimen name="pip_menu_button_size">40dp</dimen>
+ <dimen name="pip_menu_button_radius">20dp</dimen>
+ <dimen name="pip_menu_icon_size">20dp</dimen>
+ <dimen name="pip_menu_button_margin">4dp</dimen>
+ <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+ <dimen name="pip_menu_border_width">4dp</dimen>
+ <dimen name="pip_menu_border_radius">4dp</dimen>
+ <dimen name="pip_menu_outer_space">24dp</dimen>
+
+ <!-- outer space minus border width -->
+ <dimen name="pip_menu_outer_space_frame">20dp</dimen>
+
+ <dimen name="pip_menu_arrow_size">24dp</dimen>
+ <dimen name="pip_menu_arrow_elevation">5dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index c57f16f059df..c3411a837c78 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Закрити"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Розгорнути"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Налаштування"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Розділити екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Перейти далі"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Перейти назад"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Натисніть, щоб перезапустити додаток і перейти в повноекранний режим."</string>
- <string name="got_it" msgid="4428750913636945527">"Зрозуміло"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми з камерою?\nНатисніть, щоб пристосувати"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Деякі додатки найкраще працюють у вертикальній орієнтації"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Щоб максимально ефективно використовувати місце на екрані, спробуйте виконати одну з наведених нижче дій"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Щоб перейти в повноекранний режим, поверніть пристрій"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Щоб перемістити додаток, двічі торкніться області поруч із ним"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
index 74d4723d7850..7e9f54e68f54 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string>
<string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Перемістити картинку в картинці"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Розгорнути картинку в картинці"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Згорнути картинку в картинці"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Відкрити елементи керування: двічі натисніть "<annotation icon="home_icon">"HOME"</annotation></string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 97a22e77c069..a31c2be25643 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"بند کریں"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"پھیلائیں"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"مینو"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"نظرانداز کرکے اگلے پر جائیں"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"نظرانداز کرکے پچھلے پر جائیں"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string>
<string name="restart_button_description" msgid="5887656107651190519">"یہ ایپ دوبارہ شروع کرنے کے لیے تھپتھپائیں اور پوری اسکرین پر جائیں۔"</string>
- <string name="got_it" msgid="4428750913636945527">"سمجھ آ گئی"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"کیمرے کے مسائل؟\nدوبارہ فٹ کرنے کیلئے تھپتھپائیں"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"کچھ ایپس پورٹریٹ میں بہترین کام کرتی ہیں"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"اپنی اسپیس کا زیادہ سے زیادہ فائدہ اٹھانے کے لیے ان اختیارات میں سے ایک کو آزمائیں"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"پوری اسکرین پر جانے کیلئے اپنا آلہ گھمائیں"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس کے آگے دو بار تھپتھپائیں"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index 317953309947..c2ef69ff1488 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
<string name="pip_close" msgid="9135220303720555525">"‏PIP بند کریں"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string>
+ <string name="pip_move" msgid="1544227837964635439">"‏PIP کو منتقل کریں"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"‏PIP کو پھیلائیں"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"‏PIP کو سکیڑیں"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" بٹن کو دو بار دبائیں"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 4e91e7624434..2e3222560dde 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Yopish"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Yoyish"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Sozlamalar"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ajratilgan ekranga kirish"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> tasvir ustida tasvir rejimida"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ilovasi uchun bu funksiyani sozlamalar orqali faolsizlantirish mumkin."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Keyingisiga o‘tish"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Avvalgisiga qaytish"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Bu ilovani qaytadan ishga tushirish va butun ekranda ochish uchun bosing."</string>
- <string name="got_it" msgid="4428750913636945527">"OK"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string>
+ <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="6688664582871779215">"Ayrim ilovalar tik holatda ishlashga eng mos"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Muhitdan yanada samarali foydalanish uchun quyidagilardan birini sinang"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Butun ekranda ochish uchun qurilmani buring"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Qayta joylash uchun keyingi ilova ustiga ikki marta bosing"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
index ae5a647301c8..9ab95c80aa25 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string>
<string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIPni siljitish"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP funksiyasini yoyish"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP funksiyasini yopish"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Boshqaruv uchun "<annotation icon="home_icon">"ASOSIY"</annotation>" tugmani ikki marta bosing"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 169e986f7721..8f3cffecc952 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Đóng"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Mở rộng"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Cài đặt"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Truy cập chế độ chia đôi màn hình"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> đang ở chế độ ảnh trong ảnh"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Nếu bạn không muốn <xliff:g id="NAME">%s</xliff:g> sử dụng tính năng này, hãy nhấn để mở cài đặt và tắt tính năng này."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Chuyển tới mục tiếp theo"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Chuyển về mục trước"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Nhấn để khởi động lại ứng dụng này và xem ở chế độ toàn màn hình."</string>
- <string name="got_it" msgid="4428750913636945527">"Tôi hiểu"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string>
+ <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="6688664582871779215">"Một số ứng dụng hoạt động tốt nhất ở chế độ dọc"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Hãy thử một trong các tuỳ chọn sau để tận dụng không gian"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xoay thiết bị để chuyển sang chế độ toàn màn hình"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Nhấn đúp vào bên cạnh ứng dụng để đặt lại vị trí"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
index 082d12596076..146376d3cab6 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string>
<string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Di chuyển PIP (Ảnh trong ảnh)"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Mở rộng PIP (Ảnh trong ảnh)"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Thu gọn PIP (Ảnh trong ảnh)"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Nhấn đúp vào nút "<annotation icon="home_icon">" MÀN HÌNH CHÍNH "</annotation>" để mở trình đơn điều khiể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 1999703ddd2a..19a9d371e435 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"关闭"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展开"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"设置"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"进入分屏模式"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"菜单"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"跳到下一个"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"跳到上一个"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭对话泡。"</string>
<string name="restart_button_description" msgid="5887656107651190519">"点按即可重启此应用并进入全屏模式。"</string>
- <string name="got_it" msgid="4428750913636945527">"知道了"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相机有问题?\n点按即可整修"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"某些应用在纵向模式下才能发挥最佳效果"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"这些选项都有助于您最大限度地利用屏幕空间,不妨从中择一试试"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋转设备即可进入全屏模式"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在某个应用旁边连续点按两次,即可调整它的位置"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
index cb3fcf7c4c16..55407d2c699d 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string>
<string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string>
+ <string name="pip_move" msgid="1544227837964635439">"移动画中画窗口"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"展开 PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"收起 PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" 按两次"<annotation icon="home_icon">"主屏幕"</annotation>"按钮可查看相关控件"</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 f82d6d5e771b..0c40e963f2e4 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"關閉"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割螢幕"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"跳到下一個"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"跳到上一個"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string>
<string name="restart_button_description" msgid="5887656107651190519">"輕按即可重新開啟此應用程式並放大至全螢幕。"</string>
- <string name="got_it" msgid="4428750913636945527">"知道了"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題?\n輕按即可修正"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"部分應用程式需要使用直向模式才能發揮最佳效果"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"請嘗試以下選項,充分運用螢幕的畫面空間"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕按兩下即可調整位置"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
index 956243ed6e6d..15e278d8ecc2 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string>
<string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
+ <string name="pip_move" msgid="1544227837964635439">"移動畫中畫"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"展開畫中畫"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"收合畫中畫"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" 按兩下"<annotation icon="home_icon">" 主畫面按鈕"</annotation>"即可顯示控制項"</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 596e7c717aa2..8691352cf94a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"關閉"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"展開"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割畫面"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_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>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"跳到下一個"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"跳到上一個"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
@@ -44,7 +47,7 @@
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"以 30% 的螢幕空間顯示頂端畫面"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"以全螢幕顯示底部畫面"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上的任何位置"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"啟動單手模式"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"結束單手模式"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」對話框的設定"</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string>
<string name="restart_button_description" msgid="5887656107651190519">"輕觸即可重新啟動這個應用程式並進入全螢幕模式。"</string>
- <string name="got_it" msgid="4428750913636945527">"我知道了"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題嗎?\n輕觸即可修正"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"某些應用程式在直向模式下才能發揮最佳效果"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"請試試這裡的任一方式,以充分運用螢幕畫面的空間"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕觸兩下即可調整位置"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
index 08b2f4bbca89..0b17b31d23d0 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string>
<string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
+ <string name="pip_move" msgid="1544227837964635439">"移動子母畫面"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"展開子母畫面"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"收合子母畫面"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" 按兩下"<annotation icon="home_icon">"主畫面按鈕"</annotation>"即可顯示控制選項"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 3fed41f27c0a..44ffbc6afa45 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -20,6 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Uma ungafuni i-<xliff:g id="NAME">%s</xliff:g> ukuthi isebenzise lesi sici, thepha ukuze uvule izilungiselelo uphinde uyivale."</string>
@@ -28,6 +29,8 @@
<string name="pip_skip_to_next" msgid="8403429188794867653">"Yeqela kokulandelayo"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"Yeqela kokwangaphambilini"</string>
<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="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
@@ -70,5 +73,12 @@
<string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string>
<string name="restart_button_description" msgid="5887656107651190519">"Thepha ukuze uqale kabusha lolu hlelo lokusebenza uphinde uye kusikrini esigcwele."</string>
- <string name="got_it" msgid="4428750913636945527">"Ngiyezwa"</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string>
+ <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="6688664582871779215">"Amanye ama-app asebenza ngcono uma eme ngobude"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Zama enye yalezi zinketho ukuze usebenzise isikhala sakho ngokugcwele"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungezisa idivayisi yakho ukuze uye esikrinini esigcwele"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Thepha kabili eduze kwe-app ukuze uyimise kabusha"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
index 89c7f498652d..dad8c8128222 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
@@ -21,4 +21,8 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string>
<string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string>
+ <string name="pip_move" msgid="1544227837964635439">"Hambisa i-PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Nweba i-PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Goqa i-PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Chofoza kabili "<annotation icon="home_icon">" IKHAYA"</annotation>" mayelana nezilawuli"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
new file mode 100644
index 000000000000..4aaeef8afcb0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <declare-styleable name="LetterboxEduDialogActionLayout">
+ <attr name="icon" format="reference" />
+ <attr name="text" format="string" />
+ </declare-styleable>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cf596f7d15dc..4606d24d1716 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -34,6 +34,10 @@
<color name="compat_controls_background">@android:color/system_neutral1_800</color>
<color name="compat_controls_text">@android:color/system_neutral1_50</color>
+ <!-- Letterbox Education -->
+ <color name="letterbox_education_accent_primary">@android:color/system_accent1_100</color>
+ <color name="letterbox_education_text_secondary">@android:color/system_neutral2_200</color>
+
<!-- GM2 colors -->
<color name="GM2_grey_200">#E8EAED</color>
<color name="GM2_grey_700">#5F6368</color>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
new file mode 100644
index 000000000000..64b146ec3a83
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
+ <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
+ <color name="tv_pip_menu_icon_disabled">#80868B</color>
+ <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
+ <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
+ <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
+ <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+ <color name="tv_pip_menu_focus_border">#E8EAED</color>
+</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 1b8032b7077b..d416c060c86c 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -70,4 +70,30 @@
<!-- Animation duration when exit starting window: reveal app -->
<integer name="starting_window_app_reveal_anim_duration">266</integer>
+
+ <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
+ 16x16
+ </string>
+
+ <!-- The percentage of the screen width to use for the default width or height of
+ picture-in-picture windows. Regardless of the percent set here, calculated size will never
+ be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+ <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item>
+
+ <!-- The default aspect ratio for picture-in-picture windows. -->
+ <item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">
+ 1.777778
+ </item>
+
+ <!-- This is the limit for the max and min aspect ratio (1 / this value) at which the min size
+ will be used instead of an adaptive size based loosely on area. -->
+ <item name="config_pictureInPictureAspectRatioLimitForMinSize" format="float" type="dimen">
+ 1.777778
+ </item>
+
+ <!-- The default gravity for the picture-in-picture window.
+ Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x55</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index af78293eb3ea..59d03c738723 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -18,8 +18,8 @@
<dimen name="dismiss_circle_size">96dp</dimen>
<dimen name="dismiss_circle_small">60dp</dimen>
- <!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
- <dimen name="floating_dismiss_gradient_height">250dp</dimen>
+ <!-- The height of the gradient indicating the dismiss edge when moving a PIP or bubble. -->
+ <dimen name="floating_dismiss_gradient_height">548dp</dimen>
<!-- The padding around a PiP actions. -->
<dimen name="pip_action_padding">16dp</dimen>
@@ -129,6 +129,9 @@
<dimen name="bubble_dismiss_encircle_size">52dp</dimen>
<!-- Padding around the view displayed when the bubble is expanded -->
<dimen name="bubble_expanded_view_padding">16dp</dimen>
+ <!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
+ when displaying in landscape on a large screen. -->
+ <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
<!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
a slight touch slop around the expanded view. -->
<dimen name="bubble_expanded_view_slop">8dp</dimen>
@@ -136,16 +139,21 @@
If this value changes then R.dimen.bubble_expanded_view_min_height in CtsVerifier
should also be updated. -->
<dimen name="bubble_expanded_default_height">180dp</dimen>
- <!-- On large screens the width of the expanded view is restricted to this size. -->
- <dimen name="bubble_expanded_view_phone_landscape_overflow_width">412dp</dimen>
+ <!-- The width of the overflow view on large screens or in landscape on phone. -->
+ <dimen name="bubble_expanded_view_overflow_width">380dp</dimen>
<!-- Inset to apply to the icon in the overflow button. -->
<dimen name="bubble_overflow_icon_inset">30dp</dimen>
<!-- Default (and minimum) height of bubble overflow -->
<dimen name="bubble_overflow_height">480dp</dimen>
<!-- Bubble overflow padding when there are no bubbles -->
<dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
- <!-- Padding of container for overflow bubbles -->
- <dimen name="bubble_overflow_padding">15dp</dimen>
+ <!-- Horizontal padding of the overflow container. Total desired padding is 16dp but the items
+ already have 5dp added to each side. -->
+ <dimen name="bubble_overflow_container_padding_horizontal">11dp</dimen>
+ <!-- Horizontal padding between items in the overflow view, half of the desired amount. -->
+ <dimen name="bubble_overflow_item_padding_horizontal">5dp</dimen>
+ <!-- Vertical padding between items in the overflow view, half the desired amount. -->
+ <dimen name="bubble_overflow_item_padding_vertical">16dp</dimen>
<!-- Padding of label for bubble overflow view -->
<dimen name="bubble_overflow_text_padding">7dp</dimen>
<!-- Height of bubble overflow empty state illustration -->
@@ -197,8 +205,15 @@
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<dimen name="bubble_manage_menu_elevation">4dp</dimen>
- <!-- Size of user education views on large screens (phone is just match parent). -->
- <dimen name="bubbles_user_education_width_large_screen">400dp</dimen>
+ <!-- Size of manage user education views on large screens or in landscape. -->
+ <dimen name="bubbles_user_education_width">480dp</dimen>
+ <!-- Margin applied to the end of the user education views (really only matters for phone
+ since the width is match parent). -->
+ <dimen name="bubble_user_education_margin_end">24dp</dimen>
+ <!-- Padding applied to the end of the user education view. -->
+ <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>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">16dp</dimen>
@@ -213,6 +228,30 @@
+ compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). -->
<dimen name="compat_hint_padding_end">7dp</dimen>
+ <!-- The width of the size compat hint. -->
+ <dimen name="size_compat_hint_width">188dp</dimen>
+
+ <!-- The width of the camera compat hint. -->
+ <dimen name="camera_compat_hint_width">143dp</dimen>
+
+ <!-- The corner radius of the letterbox education dialog. -->
+ <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
+
+ <!-- The size of an icon in the letterbox education dialog. -->
+ <dimen name="letterbox_education_dialog_icon_size">48dp</dimen>
+
+ <!-- The fixed width of the dialog if there is enough space in the parent. -->
+ <dimen name="letterbox_education_dialog_width">472dp</dimen>
+
+ <!-- The margin between the dialog container and its parent. -->
+ <dimen name="letterbox_education_dialog_margin">16dp</dimen>
+
+ <!-- The width of each action container in the letterbox education dialog -->
+ <dimen name="letterbox_education_dialog_action_width">140dp</dimen>
+
+ <!-- The space between two actions in the letterbox education dialog -->
+ <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen>
+
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
@@ -227,4 +266,14 @@
<!-- The distance of the shift icon when early exit starting window. -->
<dimen name="starting_surface_early_exit_icon_distance">32dp</dimen>
+
+ <!-- The default minimal size of a PiP task, in both dimensions. -->
+ <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
+
+ <!--
+ The overridable minimal size of a PiP task, in both dimensions.
+ Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
+ when the pinned stack size is overridden by app via minWidth/minHeight.
+ -->
+ <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index c88fc16e218e..a24311fb1f21 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -158,4 +158,32 @@
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
<string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+ <!-- Description of the camera compat button for applying stretched issues treatment in the hint for
+ compatibility control. [CHAR LIMIT=NONE] -->
+ <string name="camera_compat_treatment_suggested_button_description">Camera issues?\nTap to refit</string>
+
+ <!-- Description of the camera compat button for reverting stretched issues treatment in the hint for
+ compatibility control. [CHAR LIMIT=NONE] -->
+ <string name="camera_compat_treatment_applied_button_description">Didn\u2019t fix it?\nTap to revert</string>
+
+ <!-- Accessibillity description of the camera dismiss button for stretched issues in the hint for
+ compatibility control. [CHAR LIMIT=NONE] -->
+ <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
+
+ <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_dialog_title">Some apps work best in portrait</string>
+
+ <!-- The subtext of the letterbox education dialog. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_dialog_subtext">Try one of these options to make the most of your space</string>
+
+ <!-- Description of the rotate screen action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_screen_rotation_text">Rotate your device to go full screen</string>
+
+ <!-- Description of the reposition app action. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_reposition_text">Double-tap next to an app to reposition it</string>
+
+ <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
+ <string name="letterbox_education_got_it">Got it</string>
+
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml
index 2dfdcabaa931..09ed9b8e52ee 100644
--- a/libs/WindowManager/Shell/res/values/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values/strings_tv.xml
@@ -30,5 +30,19 @@
<!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
<string name="pip_fullscreen">Full screen</string>
+
+ <!-- Button to move picture-in-picture (PIP) via DPAD in the PIP menu [CHAR LIMIT=30] -->
+ <string name="pip_move">Move PIP</string>
+
+ <!-- Button to expand the picture-in-picture (PIP) window [CHAR LIMIT=30] -->
+ <string name="pip_expand">Expand PIP</string>
+
+ <!-- Button to collapse/shrink the picture-in-picture (PIP) window [CHAR LIMIT=30] -->
+ <string name="pip_collapse">Collapse PIP</string>
+
+ <!-- Educative text instructing the user to double press the HOME button to access the pip
+ controls menu [CHAR LIMIT=50] -->
+ <string name="pip_edu_text"> Double press <annotation icon="home_icon"> HOME </annotation> for
+ controls </string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 908a31dc3e4e..06f4367752fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -46,11 +47,13 @@ public final class ShellCommandHandlerImpl {
private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
@@ -60,6 +63,7 @@ public final class ShellCommandHandlerImpl {
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
+ mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mRecentTasks = recentTasks;
mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
@@ -92,6 +96,9 @@ public final class ShellCommandHandlerImpl {
pw.println();
pw.println();
mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
+ pw.println();
+ pw.println();
+ mKidsModeTaskOrganizer.dump(pw, "");
}
@@ -112,8 +119,6 @@ public final class ShellCommandHandlerImpl {
return runRemoveFromSideStage(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
- case "setSideStageVisibility":
- return runSetSideStageVisibility(args, pw);
case "help":
return runHelp(pw);
default:
@@ -179,18 +184,6 @@ public final class ShellCommandHandlerImpl {
return true;
}
- private boolean runSetSideStageVisibility(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: side stage visibility should be provided as arguments");
- return false;
- }
- final Boolean visible = new Boolean(args[2]);
-
- mSplitScreenOptional.ifPresent(split -> split.setSideStageVisibility(visible));
- return true;
- }
-
private boolean runHelp(PrintWriter pw) {
pw.println("Window Manager Shell commands:");
pw.println(" help");
@@ -208,8 +201,6 @@ public final class ShellCommandHandlerImpl {
pw.println(" Enable/Disable outline on the side-stage.");
pw.println(" setSideStagePosition <SideStagePosition>");
pw.println(" Sets the position of the side-stage.");
- pw.println(" setSideStageVisibility <true/false>");
- pw.println(" Show/hide side-stage.");
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index c3ce3627fb0b..b729fe1e55dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -29,6 +29,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -48,6 +49,7 @@ public class ShellInitImpl {
private final DisplayInsetsController mDisplayInsetsController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final Optional<BubbleController> mBubblesOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<AppPairsController> mAppPairsOptional;
@@ -68,6 +70,7 @@ public class ShellInitImpl {
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
@@ -84,6 +87,7 @@ public class ShellInitImpl {
mDisplayInsetsController = displayInsetsController;
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
+ mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mBubblesOptional = bubblesOptional;
mSplitScreenOptional = splitScreenOptional;
mAppPairsOptional = appPairsOptional;
@@ -136,6 +140,9 @@ public class ShellInitImpl {
mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
mRecentTasks.ifPresent(RecentTasksController::init);
+
+ // Initialize kids mode task organizer
+ mKidsModeTaskOrganizer.initialize(mStartingWindow);
}
@ExternalThread
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 8b3a35688f11..31f0ef0192ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -98,16 +98,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
default void onTaskVanished(RunningTaskInfo taskInfo) {}
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
- /** Whether this task listener supports compat UI. */
+ /** Whether this task listener supports compat UI. */
default boolean supportCompatUI() {
// All TaskListeners should support compat UI except PIP.
return true;
}
- /** Attaches the a child window surface to the task surface. */
+ /** Attaches a child window surface to the task surface. */
default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
throw new IllegalStateException(
"This task listener doesn't support child surface attachment.");
}
+ /** Reparents a child window surface to the task surface. */
+ default void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ throw new IllegalStateException(
+ "This task listener doesn't support child surface reparent.");
+ }
default void dump(@NonNull PrintWriter pw, String prefix) {};
}
@@ -159,8 +165,12 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private StartingWindowController mStartingWindow;
/**
- * In charge of showing compat UI. Can be {@code null} if device doesn't support size
- * compat.
+ * In charge of showing compat UI. Can be {@code null} if the device doesn't support size
+ * compat or if this isn't the main {@link ShellTaskOrganizer}.
+ *
+ * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController},
+ * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be
+ * initialized with a {@code null} {@link CompatUIController}.
*/
@Nullable
private final CompatUIController mCompatUI;
@@ -190,8 +200,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@VisibleForTesting
- ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable CompatUIController compatUI,
+ protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
+ ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
Optional<RecentTasksController> recentTasks) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
@@ -458,7 +468,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
newListener.onTaskInfoChanged(taskInfo);
}
notifyLocusVisibilityIfNeeded(taskInfo);
- if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+ if (updated || !taskInfo.equalsForCompatUi(data.getTaskInfo())) {
// Notify the compat UI if the listener or task info changed.
notifyCompatUI(taskInfo, newListener);
}
@@ -607,6 +617,36 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
}
+ @Override
+ public void onCameraControlStateUpdated(
+ int taskId, @TaskInfo.CameraCompatControlState int state) {
+ final TaskAppearedInfo info;
+ synchronized (mLock) {
+ info = mTasks.get(taskId);
+ }
+ if (info == null) {
+ return;
+ }
+ updateCameraCompatControlState(info.getTaskInfo().token, state);
+ }
+
+ /** Reparents a child window surface to the task surface. */
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ final TaskListener taskListener;
+ synchronized (mLock) {
+ taskListener = mTasks.contains(taskId)
+ ? getTaskListener(mTasks.get(taskId).getTaskInfo())
+ : null;
+ }
+ if (taskListener == null) {
+ ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
+ taskId);
+ return;
+ }
+ taskListener.reparentChildSurfaceToTask(taskId, sc, t);
+ }
+
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -633,14 +673,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
// The task is vanished or doesn't support compat UI, notify to remove compat UI
// on this Task if there is any.
if (taskListener == null || !taskListener.supportCompatUI()
- || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
- mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
- null /* taskConfig */, null /* taskListener */);
+ || !taskInfo.hasCompatUI() || !taskInfo.isVisible) {
+ mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
return;
}
-
- mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
- taskInfo.configuration, taskListener);
+ mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 2f3214d1d1ab..d28a68a42b2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,6 +41,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -77,6 +78,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
+ private final TaskViewTransitions mTaskViewTransitions;
private ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
@@ -86,23 +88,33 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private boolean mIsInitialized;
private Listener mListener;
private Executor mListenerExecutor;
- private Rect mObscuredTouchRect;
+ private Region mObscuredTouchRegion;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
- public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
+ public TaskView(Context context, ShellTaskOrganizer organizer,
+ TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
mTaskOrganizer = organizer;
mShellExecutor = organizer.getExecutor();
mSyncQueue = syncQueue;
+ mTaskViewTransitions = taskViewTransitions;
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.addTaskView(this);
+ }
setUseAlpha();
getHolder().addCallback(this);
mGuard.open("release");
}
+ /** Until all users are converted, we may have mixed-use (eg. Car). */
+ private boolean isUsingShellTransitions() {
+ return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ }
+
/**
* Only one listener may be set on the view, throws an exception otherwise.
*/
@@ -129,6 +141,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
prepareActivityOptions(options, launchBounds);
LauncherApps service = mContext.getSystemService(LauncherApps.class);
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
+ mTaskViewTransitions.startTaskView(wct, this);
+ });
+ return;
+ }
try {
service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
} catch (Exception e) {
@@ -148,6 +168,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
prepareActivityOptions(options, launchBounds);
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+ mTaskViewTransitions.startTaskView(wct, this);
+ });
+ return;
+ }
try {
pendingIntent.send(mContext, 0 /* code */, fillInIntent,
null /* onFinished */, null /* handler */, null /* requiredPermission */,
@@ -174,25 +202,41 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
* @param obscuredRect the obscured region of the view.
*/
public void setObscuredTouchRect(Rect obscuredRect) {
- mObscuredTouchRect = obscuredRect;
+ mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null;
}
/**
- * Call when view position or size has changed. Do not call when animating.
+ * Indicates a region of the view that is not touchable.
+ *
+ * @param obscuredRegion the obscured region of the view.
*/
- public void onLocationChanged() {
- if (mTaskToken == null) {
- return;
- }
+ public void setObscuredTouchRegion(Region obscuredRegion) {
+ mObscuredTouchRegion = obscuredRegion;
+ }
+
+ private void onLocationChanged(WindowContainerTransaction wct) {
// Update based on the screen bounds
getBoundsOnScreen(mTmpRect);
getRootView().getBoundsOnScreen(mTmpRootRect);
if (!mTmpRootRect.contains(mTmpRect)) {
mTmpRect.offsetTo(0, 0);
}
+ wct.setBounds(mTaskToken, mTmpRect);
+ }
+
+ /**
+ * Call when view position or size has changed. Do not call when animating.
+ */
+ public void onLocationChanged() {
+ if (mTaskToken == null) {
+ 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;
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mTaskToken, mTmpRect);
+ onLocationChanged(wct);
mSyncQueue.queue(wct);
}
@@ -217,6 +261,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private void performRelease() {
getHolder().removeCallback(this);
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.removeTaskView(this);
+ }
mShellExecutor.execute(() -> {
mTaskOrganizer.removeListener(this);
resetTaskInfo();
@@ -254,6 +301,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
+ if (isUsingShellTransitions()) {
+ // Everything else handled by enter transition.
+ return;
+ }
mTaskInfo = taskInfo;
mTaskToken = taskInfo.token;
mTaskLeash = leash;
@@ -288,6 +339,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
+ // we know about -- so leave clean-up here even if shell transitions are enabled.
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
@@ -323,10 +376,20 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- if (mTaskInfo.taskId != taskId) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
+ if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
- b.setParent(mTaskLeash);
+ return mTaskLeash;
}
@Override
@@ -355,6 +418,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
// Nothing to update, task is not yet available
return;
}
+ if (isUsingShellTransitions()) {
+ mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+ return;
+ }
// Reparent the task when this surface is created
mTransaction.reparent(mTaskLeash, getSurfaceControl())
.show(mTaskLeash)
@@ -380,6 +447,11 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
return;
}
+ if (isUsingShellTransitions()) {
+ mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+ return;
+ }
+
// Unparent the task when this surface is destroyed
mTransaction.reparent(mTaskLeash, null).apply();
updateTaskVisibility();
@@ -405,8 +477,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- if (mObscuredTouchRect != null) {
- inoutInfo.touchableRegion.union(mObscuredTouchRect);
+ if (mObscuredTouchRegion != null) {
+ inoutInfo.touchableRegion.op(mObscuredTouchRegion, Region.Op.UNION);
}
}
@@ -421,4 +493,91 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
}
+
+ ActivityManager.RunningTaskInfo getTaskInfo() {
+ return mTaskInfo;
+ }
+
+ void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+
+ finishTransaction.reparent(mTaskLeash, null).apply();
+
+ if (mListener != null) {
+ final int taskId = mTaskInfo.taskId;
+ mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+ }
+ }
+
+ /**
+ * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
+ * is used instead.
+ */
+ void prepareCloseAnimation() {
+ if (mTaskToken != null) {
+ if (mListener != null) {
+ final int taskId = mTaskInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskRemovalStarted(taskId);
+ });
+ }
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+ }
+ resetTaskInfo();
+ }
+
+ void prepareOpenAnimation(final boolean newTask,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ WindowContainerTransaction wct) {
+ mTaskInfo = taskInfo;
+ mTaskToken = mTaskInfo.token;
+ mTaskLeash = leash;
+ if (mSurfaceCreated) {
+ // Surface is ready, so just reparent the task to this surface control
+ startTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .show(mTaskLeash)
+ .apply();
+ // Also reparent on finishTransaction since the finishTransaction will reparent back
+ // to its "original" parent by default.
+ finishTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .setPosition(mTaskLeash, 0, 0)
+ .apply();
+
+ // TODO: determine if this is really necessary or not
+ onLocationChanged(wct);
+ } else {
+ // The surface has already been destroyed before the task has appeared,
+ // so go ahead and hide the task entirely
+ wct.setHidden(mTaskToken, true /* hidden */);
+ // listener callback is below
+ }
+ if (newTask) {
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
+ }
+
+ if (mTaskInfo.taskDescription != null) {
+ int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
+ setResizeBackgroundColor(startTransaction, backgroundColor);
+ }
+
+ if (mListener != null) {
+ final int taskId = mTaskInfo.taskId;
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+
+ mListenerExecutor.execute(() -> {
+ if (newTask) {
+ mListener.onTaskCreated(taskId, baseActivity);
+ }
+ // Even if newTask, send a visibilityChange if the surface was destroyed.
+ if (!newTask || !mSurfaceCreated) {
+ mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+ }
+ });
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index 8286d102791e..42844b57b92a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -31,13 +31,24 @@ public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
+ private final TaskViewTransitions mTaskViewTransitions;
private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
+ ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
+ TaskViewTransitions taskViewTransitions) {
+ mTaskOrganizer = taskOrganizer;
+ mShellExecutor = shellExecutor;
+ mSyncQueue = syncQueue;
+ mTaskViewTransitions = taskViewTransitions;
+ }
+
+ public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
mSyncQueue = syncQueue;
+ mTaskViewTransitions = null;
}
public TaskViewFactory asTaskViewFactory() {
@@ -46,7 +57,7 @@ public class TaskViewFactoryController {
/** Creates an {@link TaskView} */
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
- TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue);
+ TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
executor.execute(() -> {
onCreate.accept(taskView);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
new file mode 100644
index 000000000000..83335ac24799
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/**
+ * Handles Shell Transitions that involve TaskView tasks.
+ */
+public class TaskViewTransitions implements Transitions.TransitionHandler {
+ private static final String TAG = "TaskViewTransitions";
+
+ private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
+ private final ArrayList<PendingTransition> mPending = new ArrayList<>();
+ private final Transitions mTransitions;
+ private final boolean[] mRegistered = new boolean[]{ false };
+
+ /**
+ * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
+ * in-flight (collecting) at a time (because otherwise, the operations could get merged into
+ * a single transition). So, keep a queue here until we add a queue in server-side.
+ */
+ private static class PendingTransition {
+ final @WindowManager.TransitionType int mType;
+ final WindowContainerTransaction mWct;
+ final @NonNull TaskView mTaskView;
+ IBinder mClaimed;
+
+ PendingTransition(@WindowManager.TransitionType int type,
+ @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
+ mType = type;
+ mWct = wct;
+ mTaskView = taskView;
+ }
+ }
+
+ public TaskViewTransitions(Transitions transitions) {
+ mTransitions = transitions;
+ // Defer registration until the first TaskView because we want this to be the "first" in
+ // priority when handling requests.
+ // TODO(210041388): register here once we have an explicit ordering mechanism.
+ }
+
+ void addTaskView(TaskView tv) {
+ synchronized (mRegistered) {
+ if (!mRegistered[0]) {
+ mRegistered[0] = true;
+ mTransitions.addHandler(this);
+ }
+ }
+ mTaskViews.add(tv);
+ }
+
+ void removeTaskView(TaskView tv) {
+ mTaskViews.remove(tv);
+ // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
+ }
+
+ /**
+ * Looks through the pending transitions for one matching `taskView`.
+ * @param taskView the pending transition should be for this.
+ * @param closing When true, this only returns a pending transition of the close/hide type.
+ * Otherwise it selects open/show.
+ * @param latest When true, this will only check the most-recent pending transition for the
+ * specified taskView. If it doesn't match `closing`, this will return null even
+ * if there is a match earlier. The idea behind this is to check the state of
+ * the taskviews "as if all transitions already happened".
+ */
+ private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
+ for (int i = mPending.size() - 1; i >= 0; --i) {
+ if (mPending.get(i).mTaskView != taskView) continue;
+ if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
+ return mPending.get(i);
+ }
+ if (latest) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private PendingTransition findPending(IBinder claimed) {
+ for (int i = 0; i < mPending.size(); ++i) {
+ if (mPending.get(i).mClaimed != claimed) continue;
+ return mPending.get(i);
+ }
+ return null;
+ }
+
+ /** @return whether there are pending transitions on TaskViews. */
+ public boolean hasPending() {
+ return !mPending.isEmpty();
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ return null;
+ }
+ final TaskView taskView = findTaskView(triggerTask);
+ if (taskView == null) return null;
+ // Opening types should all be initiated by shell
+ if (!Transitions.isClosingType(request.getType())) return null;
+ PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
+ if (pending == null) {
+ pending = new PendingTransition(request.getType(), null, taskView);
+ }
+ if (pending.mClaimed != null) {
+ throw new IllegalStateException("Task is closing in 2 collecting transitions?"
+ + " This state doesn't make sense");
+ }
+ pending.mClaimed = transition;
+ return new WindowContainerTransaction();
+ }
+
+ private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
+ for (int i = 0; i < mTaskViews.size(); ++i) {
+ if (mTaskViews.get(i).getTaskInfo() == null) continue;
+ if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
+ return mTaskViews.get(i);
+ }
+ }
+ return null;
+ }
+
+ void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
+ mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
+ startNextTransition();
+ }
+
+ void setTaskViewVisible(TaskView taskView, boolean visible) {
+ PendingTransition pending = findPending(taskView, !visible, true /* latest */);
+ if (pending != null) {
+ // Already opening or creating a task, so no need to do anything here.
+ return;
+ }
+ if (taskView.getTaskInfo() == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
+ pending = new PendingTransition(
+ visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
+ mPending.add(pending);
+ startNextTransition();
+ // visibility is reported in transition.
+ }
+
+ private void startNextTransition() {
+ if (mPending.isEmpty()) return;
+ final PendingTransition pending = mPending.get(0);
+ if (pending.mClaimed != null) {
+ // Wait for this to start animating.
+ return;
+ }
+ pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ PendingTransition pending = findPending(transition);
+ if (pending == null) return false;
+ mPending.remove(pending);
+ TaskView taskView = pending.mTaskView;
+ final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change chg = info.getChanges().get(i);
+ if (chg.getTaskInfo() == null) continue;
+ tasks.add(chg);
+ }
+ if (tasks.isEmpty()) {
+ Slog.e(TAG, "Got a TaskView transition with no task.");
+ return false;
+ }
+ WindowContainerTransaction wct = null;
+ for (int i = 0; i < tasks.size(); ++i) {
+ TransitionInfo.Change chg = tasks.get(i);
+ if (Transitions.isClosingType(chg.getMode())) {
+ final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
+ TaskView tv = findTaskView(chg.getTaskInfo());
+ if (tv == null) {
+ throw new IllegalStateException("TaskView transition is closing a "
+ + "non-taskview task ");
+ }
+ if (isHide) {
+ tv.prepareHideAnimation(finishTransaction);
+ } else {
+ tv.prepareCloseAnimation();
+ }
+ } else if (Transitions.isOpeningType(chg.getMode())) {
+ final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
+ if (wct == null) wct = new WindowContainerTransaction();
+ TaskView tv = taskView;
+ if (!taskIsNew) {
+ tv = findTaskView(chg.getTaskInfo());
+ if (tv == null) {
+ throw new IllegalStateException("TaskView transition is showing a "
+ + "non-taskview task ");
+ }
+ }
+ tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
+ chg.getTaskInfo(), chg.getLeash(), wct);
+ } else {
+ throw new IllegalStateException("Claimed transition isn't an opening or closing"
+ + " type: " + chg.getMode());
+ }
+ }
+ // No animation, just show it immediately.
+ startTransaction.apply();
+ finishTransaction.apply();
+ finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ startNextTransition();
+ return true;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index e344c3bea42b..e528df8c89b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.apppairs;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
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;
@@ -83,7 +82,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
public void onLeashReady(SurfaceControl leash) {
mSyncQueue.runInSync(t -> t
.show(leash)
- .setLayer(leash, SPLIT_DIVIDER_LAYER)
+ .setLayer(leash, Integer.MAX_VALUE)
.setPosition(leash,
mSplitLayout.getDividerBounds().left,
mSplitLayout.getDividerBounds().top));
@@ -275,12 +274,22 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
if (getRootTaskId() == taskId) {
- b.setParent(mRootTaskLeash);
+ return mRootTaskLeash;
} else if (getTaskId1() == taskId) {
- b.setParent(mTaskLeash1);
+ return mTaskLeash1;
} else if (getTaskId2() == taskId) {
- b.setParent(mTaskLeash2);
+ return mTaskLeash2;
} else {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
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
new file mode 100644
index 000000000000..7cf359729ee8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.view.MotionEvent;
+import android.window.BackEvent;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface for external process to get access to the Back animation related methods.
+ */
+@ExternalThread
+public interface BackAnimation {
+
+ /**
+ * Called when a {@link MotionEvent} is generated by a back gesture.
+ */
+ void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge);
+
+ /**
+ * Sets whether the back gesture is past the trigger threshold or not.
+ */
+ void setTriggerBack(boolean triggerBack);
+
+ /**
+ * Returns a binder that can be passed to an external process to update back animations.
+ */
+ default IBackAnimation createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Sets the threshold values that defining edge swipe behavior.
+ * @param triggerThreshold the min threshold to trigger back.
+ * @param progressThreshold the max threshold to keep progressing back animation.
+ */
+ void setSwipeThresholds(float triggerThreshold, float progressThreshold);
+}
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
new file mode 100644
index 000000000000..08cb252cdf43
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+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.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.BackEvent;
+import android.window.BackNavigationInfo;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the window animation run when a user initiates a back gesture.
+ */
+public class BackAnimationController implements RemoteCallable<BackAnimationController> {
+
+ private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
+ "persist.debug.back_predictability_progress_threshold";
+ // By default, enable new back dispatching without any animations.
+ private static final int BACK_PREDICTABILITY_PROP =
+ SystemProperties.getInt("persist.debug.back_predictability", 1);
+ public static final boolean IS_ENABLED = BACK_PREDICTABILITY_PROP > 0;
+ private static final int PROGRESS_THRESHOLD = SystemProperties
+ .getInt(BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP, -1);
+ private static final String TAG = "BackAnimationController";
+ @VisibleForTesting
+ boolean mEnableAnimations = (BACK_PREDICTABILITY_PROP & (1 << 1)) != 0;
+
+ /**
+ * Location of the initial touch event of the back gesture.
+ */
+ private final PointF mInitTouchLocation = new PointF();
+
+ /**
+ * Raw delta between {@link #mInitTouchLocation} and the last touch location.
+ */
+ private final Point mTouchEventDelta = new Point();
+ private final ShellExecutor mShellExecutor;
+
+ /** True when a back gesture is ongoing */
+ private boolean mBackGestureStarted = false;
+
+ /** @see #setTriggerBack(boolean) */
+ private boolean mTriggerBack;
+
+ @Nullable
+ private BackNavigationInfo mBackNavigationInfo;
+ private final SurfaceControl.Transaction mTransaction;
+ private final IActivityTaskManager mActivityTaskManager;
+ private final Context mContext;
+ @Nullable
+ private IOnBackInvokedCallback mBackToLauncherCallback;
+ private float mTriggerThreshold;
+ private float mProgressThreshold;
+
+ public BackAnimationController(
+ @ShellMainThread ShellExecutor shellExecutor,
+ Context context) {
+ this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+ context);
+ }
+
+ @VisibleForTesting
+ BackAnimationController(@NonNull ShellExecutor shellExecutor,
+ @NonNull SurfaceControl.Transaction transaction,
+ @NonNull IActivityTaskManager activityTaskManager,
+ Context context) {
+ mShellExecutor = shellExecutor;
+ mTransaction = transaction;
+ mActivityTaskManager = activityTaskManager;
+ mContext = context;
+ }
+
+ public BackAnimation getBackAnimationImpl() {
+ return mBackAnimation;
+ }
+
+ private final BackAnimation mBackAnimation = new BackAnimationImpl();
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mShellExecutor;
+ }
+
+ private class BackAnimationImpl implements BackAnimation {
+ private IBackAnimationImpl mBackAnimation;
+
+ @Override
+ public IBackAnimation createExternalInterface() {
+ if (mBackAnimation != null) {
+ mBackAnimation.invalidate();
+ }
+ mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
+ return mBackAnimation;
+ }
+
+ @Override
+ public void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+ mShellExecutor.execute(() -> onMotionEvent(event, swipeEdge));
+ }
+
+ @Override
+ public void setTriggerBack(boolean triggerBack) {
+ mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack));
+ }
+
+ @Override
+ public void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
+ mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
+ triggerThreshold, progressThreshold));
+ }
+ }
+
+ private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private BackAnimationController mController;
+
+ IBackAnimationImpl(BackAnimationController controller) {
+ mController = controller;
+ }
+
+ @Override
+ public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
+ (controller) -> mController.setBackToLauncherCallback(callback));
+ }
+
+ @Override
+ public void clearBackToLauncherCallback() {
+ executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
+ (controller) -> mController.clearBackToLauncherCallback());
+ }
+
+ @Override
+ public void onBackToLauncherAnimationFinished() {
+ executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished",
+ (controller) -> mController.onBackToLauncherAnimationFinished());
+ }
+
+ void invalidate() {
+ mController = null;
+ }
+ }
+
+ @VisibleForTesting
+ void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ mBackToLauncherCallback = callback;
+ }
+
+ private void clearBackToLauncherCallback() {
+ mBackToLauncherCallback = null;
+ }
+
+ private void onBackToLauncherAnimationFinished() {
+ if (mBackNavigationInfo != null) {
+ IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(callback);
+ } else {
+ dispatchOnBackCancelled(callback);
+ }
+ }
+ finishAnimation();
+ }
+
+ /**
+ * Called when a new motion event needs to be transferred to this
+ * {@link BackAnimationController}
+ */
+ public void onMotionEvent(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ initAnimation(event);
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ onMove(event, swipeEdge);
+ } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ onGestureFinished();
+ }
+ }
+
+ private void initAnimation(MotionEvent event) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
+ if (mBackGestureStarted) {
+ Log.e(TAG, "Animation is being initialized but is already started.");
+ return;
+ }
+
+ if (mBackNavigationInfo != null) {
+ finishAnimation();
+ }
+ mInitTouchLocation.set(event.getX(), event.getY());
+ mBackGestureStarted = true;
+
+ try {
+ mBackNavigationInfo = mActivityTaskManager.startBackNavigation();
+ onBackNavigationInfoReceived(mBackNavigationInfo);
+ } catch (RemoteException remoteException) {
+ Log.e(TAG, "Failed to initAnimation", remoteException);
+ finishAnimation();
+ }
+ }
+
+ private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
+ if (backNavigationInfo == null) {
+ Log.e(TAG, "Received BackNavigationInfo is null.");
+ finishAnimation();
+ return;
+ }
+ int backType = backNavigationInfo.getType();
+ IOnBackInvokedCallback targetCallback = null;
+ if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+ HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
+ if (hardwareBuffer != null) {
+ displayTargetScreenshot(hardwareBuffer,
+ backNavigationInfo.getTaskWindowConfiguration());
+ }
+ mTransaction.apply();
+ } else if (shouldDispatchToLauncher(backType)) {
+ targetCallback = mBackToLauncherCallback;
+ } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ }
+ dispatchOnBackStarted(targetCallback);
+ }
+
+ /**
+ * Display the screenshot of the activity beneath.
+ *
+ * @param hardwareBuffer The buffer containing the screenshot.
+ */
+ private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer,
+ WindowConfiguration taskWindowConfiguration) {
+ SurfaceControl screenshotSurface =
+ mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface();
+ if (screenshotSurface == null) {
+ Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. ");
+ return;
+ }
+
+ // Scale the buffer to fill the whole Task
+ float sx = 1;
+ float sy = 1;
+ float w = taskWindowConfiguration.getBounds().width();
+ float h = taskWindowConfiguration.getBounds().height();
+
+ if (w != hardwareBuffer.getWidth()) {
+ sx = w / hardwareBuffer.getWidth();
+ }
+
+ if (h != hardwareBuffer.getHeight()) {
+ sy = h / hardwareBuffer.getHeight();
+ }
+ mTransaction.setScale(screenshotSurface, sx, sy);
+ mTransaction.setBuffer(screenshotSurface, hardwareBuffer);
+ mTransaction.setVisibility(screenshotSurface, true);
+ }
+
+ private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+ if (!mBackGestureStarted || mBackNavigationInfo == null) {
+ return;
+ }
+ int deltaX = Math.round(event.getX() - mInitTouchLocation.x);
+ int deltaY = Math.round(event.getY() - mInitTouchLocation.y);
+ ProtoLog.v(WM_SHELL_BACK_PREVIEW, "Runner move: %d %d", deltaX, deltaY);
+ float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
+ float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
+ int backType = mBackNavigationInfo.getType();
+ RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
+
+ BackEvent backEvent = new BackEvent(0, 0, progress, swipeEdge, animationTarget);
+ IOnBackInvokedCallback targetCallback = null;
+ if (shouldDispatchToLauncher(backType)) {
+ targetCallback = mBackToLauncherCallback;
+ } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
+ || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+ if (animationTarget != null) {
+ mTransaction.setPosition(animationTarget.leash, deltaX, deltaY);
+ mTouchEventDelta.set(deltaX, deltaY);
+ mTransaction.apply();
+ }
+ } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ }
+ dispatchOnBackProgressed(targetCallback, backEvent);
+ }
+
+ private void onGestureFinished() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted || mBackNavigationInfo == null) {
+ return;
+ }
+ int backType = mBackNavigationInfo.getType();
+ boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType);
+ IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
+ ? mBackToLauncherCallback
+ : mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(targetCallback);
+ } else {
+ dispatchOnBackCancelled(targetCallback);
+ }
+ if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ finishAnimation();
+ } else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ && !shouldDispatchToLauncher) {
+ // Launcher callback missing. Simply finish animation.
+ finishAnimation();
+ } else if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY
+ || backType == BackNavigationInfo.TYPE_CROSS_TASK) {
+ if (mTriggerBack) {
+ prepareTransition();
+ } else {
+ resetPositionAnimated();
+ }
+ }
+ }
+
+ private boolean shouldDispatchToLauncher(int backType) {
+ return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ && mBackToLauncherCallback != null
+ && mEnableAnimations;
+ }
+
+ @VisibleForTesting
+ void setEnableAnimations(boolean shouldEnable) {
+ mEnableAnimations = shouldEnable;
+ }
+
+ private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ try {
+ callback.onBackStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchOnBackStarted error: ", e);
+ }
+ }
+
+ private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ try {
+ callback.onBackInvoked();
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchOnBackInvoked error: ", e);
+ }
+ }
+
+ private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ try {
+ callback.onBackCancelled();
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchOnBackCancelled error: ", e);
+ }
+ }
+
+ private static void dispatchOnBackProgressed(
+ IOnBackInvokedCallback callback, BackEvent backEvent) {
+ if (callback == null) {
+ return;
+ }
+ try {
+ callback.onBackProgressed(backEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchOnBackProgressed error: ", e);
+ }
+ }
+
+ /**
+ * Animate the top window leash to its initial position.
+ */
+ private void resetPositionAnimated() {
+ mBackGestureStarted = false;
+ // TODO(208786853) Handle overlap with a new coming gesture.
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Runner: Back not triggered, cancelling animation "
+ + "mLastPos=%s mInitTouch=%s", mTouchEventDelta, mInitTouchLocation);
+
+ // TODO(208427216) : Replace placeholder animation with an actual one.
+ ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f).setDuration(200);
+ animation.addUpdateListener(animation1 -> {
+ if (mBackNavigationInfo == null) {
+ return;
+ }
+ float fraction = animation1.getAnimatedFraction();
+ int deltaX = Math.round(mTouchEventDelta.x - (mTouchEventDelta.x * fraction));
+ int deltaY = Math.round(mTouchEventDelta.y - (mTouchEventDelta.y * fraction));
+ RemoteAnimationTarget animationTarget =
+ mBackNavigationInfo.getDepartingAnimationTarget();
+ if (animationTarget != null) {
+ mTransaction.setPosition(animationTarget.leash, deltaX, deltaY);
+ mTransaction.apply();
+ }
+ });
+
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onAnimationEnd");
+ finishAnimation();
+ }
+ });
+ animation.start();
+ }
+
+ private void prepareTransition() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "prepareTransition()");
+ mTriggerBack = false;
+ mBackGestureStarted = false;
+ }
+
+ /**
+ * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
+ */
+ public void setTriggerBack(boolean triggerBack) {
+ mTriggerBack = triggerBack;
+ }
+
+ private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
+ mProgressThreshold = progressThreshold;
+ mTriggerThreshold = triggerThreshold;
+ }
+
+ private void finishAnimation() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
+ mBackGestureStarted = false;
+ mTouchEventDelta.set(0, 0);
+ mInitTouchLocation.set(0, 0);
+ BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
+ boolean triggerBack = mTriggerBack;
+ mBackNavigationInfo = null;
+ mTriggerBack = false;
+ if (backNavigationInfo == null) {
+ return;
+ }
+ RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
+ if (animationTarget != null) {
+ if (animationTarget.leash != null && animationTarget.leash.isValid()) {
+ mTransaction.remove(animationTarget.leash);
+ }
+ }
+ SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
+ if (screenshotSurface != null && screenshotSurface.isValid()) {
+ mTransaction.remove(screenshotSurface);
+ }
+ mTransaction.apply();
+ backNavigationInfo.onBackNavigationFinished(triggerBack);
+ }
+}
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
new file mode 100644
index 000000000000..6311f879fd45
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.back;
+
+import android.window.IOnBackInvokedCallback;
+
+/**
+ * Interface for Launcher process to register back invocation callbacks.
+ */
+interface IBackAnimation {
+
+ /**
+ * Sets a {@link IOnBackInvokedCallback} to be invoked when
+ * back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}.
+ */
+ void setBackToLauncherCallback(in IOnBackInvokedCallback callback);
+
+ /**
+ * Clears the previously registered {@link IOnBackInvokedCallback}.
+ */
+ void clearBackToLauncherCallback();
+
+ /**
+ * Notifies Shell that the back to launcher animation has fully finished
+ * (including the transition animation that runs after the finger is lifted).
+ *
+ * At this point the top window leash (if one was created) should be ready to be released.
+ * //TODO: Remove once we play the transition animation through shell transitions.
+ */
+ void onBackToLauncherAnimationFinished();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index d92e2ccc77bd..3876533a922e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -15,27 +15,27 @@
*/
package com.android.wm.shell.bubbles;
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Bitmap;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.PathParser;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import java.util.EnumSet;
@@ -47,14 +47,12 @@ import java.util.EnumSet;
* Badge = the icon associated with the app that created this bubble, this will show work profile
* badge if appropriate.
*/
-public class BadgedImageView extends ImageView {
+public class BadgedImageView extends ConstraintLayout {
/** Same value as Launcher3 dot code */
public static final float WHITE_SCRIM_ALPHA = 0.54f;
/** Same as value in Launcher3 IconShape */
public static final int DEFAULT_PATH_SIZE = 100;
- /** Same as value in Launcher3 BaseIconFactory */
- private static final float ICON_BADGE_SCALE = 0.444f;
/**
* Flags that suppress the visibility of the 'new' dot, for one reason or another. If any of
@@ -74,6 +72,9 @@ public class BadgedImageView extends ImageView {
private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
+ private final ImageView mBubbleIcon;
+ private final ImageView mAppIcon;
+
private float mDotScale = 0f;
private float mAnimatingToDotScale = 0f;
private boolean mDotIsAnimating = false;
@@ -86,7 +87,6 @@ public class BadgedImageView extends ImageView {
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
- private Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
private Rect mTempBounds = new Rect();
public BadgedImageView(Context context) {
@@ -104,6 +104,19 @@ public class BadgedImageView extends ImageView {
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ // We manage positioning the badge ourselves
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
+
+ LayoutInflater.from(context).inflate(R.layout.badged_image_view, this);
+
+ mBubbleIcon = findViewById(R.id.icon_view);
+ mAppIcon = findViewById(R.id.app_icon_view);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src},
+ defStyleAttr, defStyleRes);
+ mBubbleIcon.setImageResource(ta.getResourceId(0, 0));
+ ta.recycle();
+
mDrawParams = new DotRenderer.DrawParams();
setFocusable(true);
@@ -135,7 +148,6 @@ public class BadgedImageView extends ImageView {
public void showDotAndBadge(boolean onLeft) {
removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
animateDotBadgePositions(onLeft);
-
}
public void hideDotAndBadge(boolean onLeft) {
@@ -149,6 +161,8 @@ public class BadgedImageView extends ImageView {
*/
public void setRenderedBubble(BubbleViewProvider bubble) {
mBubble = bubble;
+ mBubbleIcon.setImageBitmap(bubble.getBubbleIcon());
+ mAppIcon.setImageBitmap(bubble.getAppBadge());
if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) {
hideBadge();
} else {
@@ -159,8 +173,8 @@ public class BadgedImageView extends ImageView {
}
@Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
if (!shouldDrawDot()) {
return;
@@ -176,6 +190,20 @@ public class BadgedImageView extends ImageView {
mDotRenderer.draw(canvas, mDrawParams);
}
+ /**
+ * Set drawable resource shown as the icon
+ */
+ public void setIconImageResource(@DrawableRes int drawable) {
+ mBubbleIcon.setImageResource(drawable);
+ }
+
+ /**
+ * Get icon drawable
+ */
+ public Drawable getIconDrawable() {
+ return mBubbleIcon.getDrawable();
+ }
+
/** Adds a dot suppression flag, updating dot visibility if needed. */
void addDotSuppressionFlag(SuppressionFlag flag) {
if (mDotSuppressionFlags.add(flag)) {
@@ -279,7 +307,6 @@ public class BadgedImageView extends ImageView {
showBadge();
}
-
/** Whether to draw the dot in onDraw(). */
private boolean shouldDrawDot() {
// Always render the dot if it's animating, since it could be animating out. Otherwise, show
@@ -323,31 +350,26 @@ public class BadgedImageView extends ImageView {
}
void showBadge() {
- Bitmap badge = mBubble.getAppBadge();
- if (badge == null) {
- setImageBitmap(mBubble.getBubbleIcon());
+ if (mBubble.getAppBadge() == null) {
+ mAppIcon.setVisibility(GONE);
return;
}
- Canvas bubbleCanvas = new Canvas();
- Bitmap noBadgeBubble = mBubble.getBubbleIcon();
- Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
-
- bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
- bubbleCanvas.setBitmap(bubble);
- final int bubbleSize = bubble.getWidth();
- final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
- Rect dest = new Rect();
+ int translationX;
if (mOnLeft) {
- dest.set(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
+ translationX = -(mBubbleIcon.getWidth() - mAppIcon.getWidth());
} else {
- dest.set(bubbleSize - badgeSize, bubbleSize - badgeSize, bubbleSize, bubbleSize);
+ translationX = 0;
}
- bubbleCanvas.drawBitmap(badge, null /* src */, dest, mPaint);
- bubbleCanvas.setBitmap(null);
- setImageBitmap(bubble);
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(VISIBLE);
}
void hideBadge() {
- setImageBitmap(mBubble.getBubbleIcon());
+ mAppIcon.setVisibility(GONE);
+ }
+
+ @Override
+ public String toString() {
+ return "BadgedImageView{" + mBubble + "}";
}
}
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 8d43f1375a8c..cf4647a09a58 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
@@ -108,6 +108,8 @@ public class Bubble implements BubbleViewProvider {
private Bitmap mBubbleBitmap;
// The app badge for the bubble
private Bitmap mBadgeBitmap;
+ // App badge without any markings for important conversations
+ private Bitmap mRawBadgeBitmap;
private int mDotColor;
private Path mDotPath;
private int mFlags;
@@ -248,6 +250,11 @@ public class Bubble implements BubbleViewProvider {
}
@Override
+ public Bitmap getRawAppBadge() {
+ return mRawBadgeBitmap;
+ }
+
+ @Override
public int getDotColor() {
return mDotColor;
}
@@ -357,13 +364,15 @@ public class Bubble implements BubbleViewProvider {
* @param context the context for the bubble.
* @param controller the bubble controller.
* @param stackView the stackView the bubble is eventually added to.
- * @param iconFactory the iconfactory use to create badged images for the bubble.
+ * @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,
BubbleIconFactory iconFactory,
+ BubbleBadgeIconFactory badgeIconFactory,
boolean skipInflation) {
if (isBubbleLoading()) {
mInflationTask.cancel(true /* mayInterruptIfRunning */);
@@ -373,6 +382,7 @@ public class Bubble implements BubbleViewProvider {
controller,
stackView,
iconFactory,
+ badgeIconFactory,
skipInflation,
callback,
mMainExecutor);
@@ -409,6 +419,7 @@ public class Bubble implements BubbleViewProvider {
mFlyoutMessage = info.flyoutMessage;
mBadgeBitmap = info.badgeBitmap;
+ mRawBadgeBitmap = info.mRawBadgeBitmap;
mBubbleBitmap = info.bubbleBitmap;
mDotColor = info.dotColor;
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
new file mode 100644
index 000000000000..4eeb20769e09
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -0,0 +1,121 @@
+/*
+ * 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.Paint;
+import android.graphics.Path;
+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.launcher3.icons.ShadowGenerator;
+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) {
+ ShadowGenerator shadowGenerator = new ShadowGenerator(mIconBitmapSize);
+ Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mIconBitmapSize);
+
+ if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
+ userBadgedBitmap = Bitmap.createScaledBitmap(
+ getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */
+ userBadgedAppIcon.getIntrinsicWidth()),
+ mIconBitmapSize, mIconBitmapSize, /* filter */ true);
+ }
+
+ if (isImportantConversation) {
+ final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width);
+ final int importantConversationColor = mContext.getResources().getColor(
+ R.color.important_conversation, null);
+ Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
+ userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
+ Canvas c = new Canvas(badgeAndRing);
+
+ Paint ringPaint = new Paint();
+ ringPaint.setStyle(Paint.Style.FILL);
+ ringPaint.setColor(importantConversationColor);
+ ringPaint.setAntiAlias(true);
+ c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2, ringPaint);
+
+ final int bitmapTop = (int) ringStrokeWidth;
+ final int bitmapLeft = (int) ringStrokeWidth;
+ final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth;
+ final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth;
+
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth,
+ bitmapHeight, /* filter */ true);
+ c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null);
+
+ shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c);
+ return createIconBitmap(badgeAndRing);
+ } else {
+ Canvas c = new Canvas();
+ c.setBitmap(userBadgedBitmap);
+ shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+ return createIconBitmap(userBadgedBitmap);
+ }
+ }
+
+ private Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) {
+ Drawable foreground = icon.getForeground();
+ Drawable background = icon.getBackground();
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas();
+ canvas.setBitmap(bitmap);
+
+ // Clip canvas to circle.
+ Path circlePath = new Path();
+ circlePath.addCircle(/* x */ size / 2f,
+ /* y */ size / 2f,
+ /* radius */ size / 2f,
+ Path.Direction.CW);
+ canvas.clipPath(circlePath);
+
+ // Draw background.
+ background.setBounds(0, 0, size, size);
+ background.draw(canvas);
+
+ // Draw foreground. The foreground and background drawables are derived from adaptive icons
+ // Some icon shapes fill more space than others, so adaptive icons are normalized to about
+ // the same size. This size is smaller than the original bounds, so we estimate
+ // the difference in this offset.
+ int offset = size / 5;
+ foreground.setBounds(-offset, -offset, size + offset, size + offset);
+ foreground.draw(canvas);
+
+ canvas.setBitmap(null);
+ return bitmap;
+ }
+}
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 ec59fad3e95b..d2a1c55d1c29 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
@@ -17,11 +17,14 @@
package com.android.wm.shell.bubbles;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM;
@@ -42,8 +45,12 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -80,6 +87,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
@@ -88,6 +96,8 @@ import com.android.wm.shell.common.ShellExecutor;
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.onehanded.OneHandedController;
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import java.io.FileDescriptor;
@@ -97,6 +107,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -123,6 +134,10 @@ public class BubbleController {
public static final String RIGHT_POSITION = "Right";
public static final String BOTTOM_POSITION = "Bottom";
+ // Should match with PhoneWindowManager
+ private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -136,6 +151,7 @@ public class BubbleController {
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
+ private final TaskViewTransitions mTaskViewTransitions;
private final SyncTransactionQueue mSyncQueue;
// Used to post to main UI thread
@@ -146,6 +162,7 @@ public class BubbleController {
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
private BubbleIconFactory mBubbleIconFactory;
+ private BubbleBadgeIconFactory mBubbleBadgeIconFactory;
private BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
@@ -196,6 +213,9 @@ public class BubbleController {
/** True when user is in status bar unlock shade. */
private boolean mIsStatusBarShade = true;
+ /** One handed mode controller to register transition listener. */
+ private Optional<OneHandedController> mOneHandedOptional;
+
/**
* Creates an instance of the BubbleController.
*/
@@ -210,8 +230,10 @@ public class BubbleController {
UiEventLogger uiEventLogger,
ShellTaskOrganizer organizer,
DisplayController displayController,
+ Optional<OneHandedController> oneHandedOptional,
ShellExecutor mainExecutor,
Handler mainHandler,
+ TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
BubbleLogger logger = new BubbleLogger(uiEventLogger);
BubblePositioner positioner = new BubblePositioner(context, windowManager);
@@ -219,8 +241,9 @@ public class BubbleController {
return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
- logger, taskStackListener, organizer, positioner, displayController, mainExecutor,
- mainHandler, syncQueue);
+ logger, taskStackListener, organizer, positioner, displayController,
+ oneHandedOptional, mainExecutor, mainHandler, taskViewTransitions,
+ syncQueue);
}
/**
@@ -241,8 +264,10 @@ public class BubbleController {
ShellTaskOrganizer organizer,
BubblePositioner positioner,
DisplayController displayController,
+ Optional<OneHandedController> oneHandedOptional,
ShellExecutor mainExecutor,
Handler mainHandler,
+ TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
mContext = context;
mLauncherApps = launcherApps;
@@ -265,10 +290,32 @@ public class BubbleController {
mBubbleData = data;
mSavedBubbleKeysPerUser = new SparseSetArray<>();
mBubbleIconFactory = new BubbleIconFactory(context);
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
mDisplayController = displayController;
+ mTaskViewTransitions = taskViewTransitions;
+ mOneHandedOptional = oneHandedOptional;
mSyncQueue = syncQueue;
}
+ private void registerOneHandedState(OneHandedController oneHanded) {
+ oneHanded.registerTransitionCallback(
+ new OneHandedTransitionCallback() {
+ @Override
+ public void onStartFinished(Rect bounds) {
+ if (mStackView != null) {
+ mStackView.onVerticalOffsetChanged(bounds.top);
+ }
+ }
+
+ @Override
+ public void onStopFinished(Rect bounds) {
+ if (mStackView != null) {
+ mStackView.onVerticalOffsetChanged(bounds.top);
+ }
+ }
+ });
+ }
+
public void initialize() {
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged);
@@ -336,23 +383,15 @@ public class BubbleController {
mTaskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onTaskMovedToFront(int taskId) {
- if (mSysuiProxy == null) {
- return;
- }
-
- mSysuiProxy.isNotificationShadeExpand((expand) -> {
- mMainExecutor.execute(() -> {
- int expandedId = INVALID_TASK_ID;
- if (mStackView != null && mStackView.getExpandedBubble() != null
- && isStackExpanded() && !mStackView.isExpansionAnimating()
- && !expand) {
- expandedId = mStackView.getExpandedBubble().getTaskId();
- }
-
- if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
- mBubbleData.setExpanded(false);
- }
- });
+ mMainExecutor.execute(() -> {
+ int expandedId = INVALID_TASK_ID;
+ if (mStackView != null && mStackView.getExpandedBubble() != null
+ && isStackExpanded() && !mStackView.isExpansionAnimating()) {
+ expandedId = mStackView.getExpandedBubble().getTaskId();
+ }
+ if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
+ mBubbleData.setExpanded(false);
+ }
});
}
@@ -383,7 +422,6 @@ public class BubbleController {
WindowContainerTransaction t) {
// This is triggered right before the rotation is applied
if (fromRotation != toRotation) {
- mBubblePositioner.setRotation(toRotation);
if (mStackView != null) {
// Layout listener set on stackView will update the positioner
// once the rotation is applied
@@ -392,6 +430,8 @@ public class BubbleController {
}
}
});
+
+ mOneHandedOptional.ifPresent(this::registerOneHandedState);
}
@VisibleForTesting
@@ -464,6 +504,7 @@ public class BubbleController {
}
mStackView.updateStackPosition();
mBubbleIconFactory = new BubbleIconFactory(mContext);
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) {
@@ -570,8 +611,13 @@ public class BubbleController {
return mSyncQueue;
}
- /** Contains information to help position things on the screen. */
- BubblePositioner getPositioner() {
+ TaskViewTransitions getTaskViewTransitions() {
+ return mTaskViewTransitions;
+ }
+
+ /** Contains information to help position things on the screen. */
+ @VisibleForTesting
+ public BubblePositioner getPositioner() {
return mBubblePositioner;
}
@@ -613,8 +659,8 @@ public class BubbleController {
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
mWmLayoutParams.setTrustedOverlay();
@@ -628,6 +674,7 @@ public class BubbleController {
try {
mAddedToWindowManager = true;
+ registerBroadcastReceiver();
mBubbleData.getOverflow().initialize(this);
mWindowManager.addView(mStackView, mWmLayoutParams);
mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
@@ -670,6 +717,7 @@ public class BubbleController {
try {
mAddedToWindowManager = false;
+ mContext.unregisterReceiver(mBroadcastReceiver);
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mBubbleData.getOverflow().cleanUpExpandedState();
@@ -683,11 +731,34 @@ public class BubbleController {
}
}
+ private void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!isStackExpanded()) return; // Nothing to do
+
+ String action = intent.getAction();
+ String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
+ if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mMainExecutor.execute(() -> collapseStack());
+ }
+ }
+ };
+
/**
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- void onAllBubblesAnimatedOut() {
+ @VisibleForTesting
+ public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
@@ -704,7 +775,7 @@ public class BubbleController {
// First clear any existing keys that might be stored.
mSavedBubbleKeysPerUser.remove(userId);
// Add in all active bubbles for the current user.
- for (Bubble bubble: mBubbleData.getBubbles()) {
+ for (Bubble bubble : mBubbleData.getBubbles()) {
mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
}
}
@@ -738,13 +809,17 @@ public class BubbleController {
mStackView.onThemeChanged();
}
mBubbleIconFactory = new BubbleIconFactory(mContext);
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+
// Reload each bubble
- for (Bubble b: mBubbleData.getBubbles()) {
+ for (Bubble b : mBubbleData.getBubbles()) {
b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
+ mBubbleBadgeIconFactory,
false /* skipInflation */);
}
- for (Bubble b: mBubbleData.getOverflowBubbles()) {
+ for (Bubble b : mBubbleData.getOverflowBubbles()) {
b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
+ mBubbleBadgeIconFactory,
false /* skipInflation */);
}
}
@@ -760,6 +835,7 @@ public class BubbleController {
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
mBubbleData.onMaxBubblesChanged();
mBubbleIconFactory = new BubbleIconFactory(mContext);
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
if (newConfig.fontScale != mFontScale) {
@@ -921,7 +997,8 @@ public class BubbleController {
}
bubble.inflate(
(b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
- mContext, this, mStackView, mBubbleIconFactory, true /* skipInflation */);
+ mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
+ true /* skipInflation */);
});
return null;
});
@@ -930,9 +1007,9 @@ public class BubbleController {
/**
* Adds or updates a bubble associated with the provided notification entry.
*
- * @param notif the notification associated with this bubble.
+ * @param notif the notification associated with this bubble.
* @param suppressFlyout this bubble suppress flyout or not.
- * @param showInShade this bubble show in shade or not.
+ * @param showInShade this bubble show in shade or not.
*/
@VisibleForTesting
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
@@ -940,11 +1017,17 @@ public class BubbleController {
mSysuiProxy.setNotificationInterruption(notif.getKey());
if (!notif.getRanking().isTextChanged()
&& (notif.getBubbleMetadata() != null
- && !notif.getBubbleMetadata().getAutoExpandBubble())
+ && !notif.getBubbleMetadata().getAutoExpandBubble())
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
// Update the bubble but don't promote it out of overflow
Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
b.setEntry(notif);
+ } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
+ // Update the bubble but don't promote it out of overflow
+ Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
+ if (b != null) {
+ b.setEntry(notif);
+ }
} else {
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
inflateAndAdd(bubble, suppressFlyout, showInShade);
@@ -956,7 +1039,8 @@ public class BubbleController {
ensureStackViewCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
- mContext, this, mStackView, mBubbleIconFactory, false /* skipInflation */);
+ mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
+ false /* skipInflation */);
}
/**
@@ -1042,6 +1126,24 @@ public class BubbleController {
}
}
+ @VisibleForTesting
+ public void onNotificationChannelModified(String pkg, UserHandle user,
+ NotificationChannel channel, int modificationType) {
+ // Only query overflow bubbles here because active bubbles will have an active notification
+ // and channel changes we care about would result in a ranking update.
+ List<Bubble> overflowBubbles = new ArrayList<>(mBubbleData.getOverflowBubbles());
+ for (int i = 0; i < overflowBubbles.size(); i++) {
+ Bubble b = overflowBubbles.get(i);
+ if (Objects.equals(b.getShortcutId(), channel.getConversationId())
+ && b.getPackageName().equals(pkg)
+ && b.getUser().getIdentifier() == user.getIdentifier()) {
+ if (!channel.canBubble() || channel.isDeleted()) {
+ mBubbleData.dismissBubbleWithKey(b.getKey(), DISMISS_NO_LONGER_BUBBLE);
+ }
+ }
+ }
+ }
+
/**
* Retrieves any bubbles that are part of the notification group represented by the provided
* group key.
@@ -1099,6 +1201,18 @@ public class BubbleController {
@Override
public void applyUpdate(BubbleData.Update update) {
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
+ + " bubbleRemoved="
+ + (update.removedBubbles != null && update.removedBubbles.size() > 0)
+ + " bubbleUpdated=" + (update.updatedBubble != null)
+ + " orderChanged=" + update.orderChanged
+ + " expandedChanged=" + update.expandedChanged
+ + " selectionChanged=" + update.selectionChanged
+ + " suppressed=" + (update.suppressedBubble != null)
+ + " unsuppressed=" + (update.unsuppressedBubble != null));
+ }
+
ensureStackViewCreated();
// Lazy load overflow bubbles from disk
@@ -1178,6 +1292,14 @@ public class BubbleController {
mStackView.updateBubble(update.updatedBubble);
}
+ if (update.suppressedBubble != null && mStackView != null) {
+ mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+ }
+
+ if (update.unsuppressedBubble != null && mStackView != null) {
+ mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+ }
+
// 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) {
@@ -1192,14 +1314,6 @@ public class BubbleController {
}
}
- if (update.suppressedBubble != null && mStackView != null) {
- mStackView.setBubbleVisibility(update.suppressedBubble, false);
- }
-
- if (update.unsuppressedBubble != null && mStackView != null) {
- mStackView.setBubbleVisibility(update.unsuppressedBubble, true);
- }
-
// Expanding? Apply this last.
if (update.expandedChanged && update.expanded) {
if (mStackView != null) {
@@ -1279,6 +1393,7 @@ public class BubbleController {
* 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
*/
public void updateStack() {
if (mStackView == null) {
@@ -1296,6 +1411,8 @@ public class BubbleController {
}
mStackView.updateContentDescription();
+
+ mStackView.updateBubblesAcessibillityStates();
}
@VisibleForTesting
@@ -1324,7 +1441,7 @@ public class BubbleController {
* that should filter out any invalid bubbles, but should protect SysUI side just in case.
*
* @param context the context to use.
- * @param entry the entry to bubble.
+ * @param entry the entry to bubble.
*/
static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
PendingIntent intent = entry.getBubbleMetadata() != null
@@ -1457,7 +1574,7 @@ public class BubbleController {
String groupKey) {
return mSuppressedBubbleKeys.contains(key)
|| (mSuppressedGroupToNotifKeys.containsKey(groupKey)
- && key.equals(mSuppressedGroupToNotifKeys.get(groupKey)));
+ && key.equals(mSuppressedGroupToNotifKeys.get(groupKey)));
}
@Nullable
@@ -1617,6 +1734,19 @@ public class BubbleController {
}
@Override
+ public void onNotificationChannelModified(String pkg,
+ UserHandle user, NotificationChannel channel, int modificationType) {
+ // Bubbles only cares about updates or deletions.
+ if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED
+ || modificationType == NOTIFICATION_CHANNEL_OR_GROUP_DELETED) {
+ mMainExecutor.execute(() -> {
+ BubbleController.this.onNotificationChannelModified(pkg, user, channel,
+ modificationType);
+ });
+ }
+ }
+
+ @Override
public void onStatusBarVisibilityChanged(boolean visible) {
mMainExecutor.execute(() -> {
BubbleController.this.onStatusBarVisibilityChanged(visible);
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 cd635c10fd8e..9961ad71e64a 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
@@ -224,7 +224,8 @@ public class BubbleData {
}
public boolean hasAnyBubbleWithKey(String key) {
- return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key);
+ return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key)
+ || hasSuppressedBubbleWithKey(key);
}
public boolean hasBubbleInStackWithKey(String key) {
@@ -235,6 +236,20 @@ public class BubbleData {
return getOverflowBubbleWithKey(key) != null;
}
+ /**
+ * Check if there are any bubbles suppressed with the given notification <code>key</code>
+ */
+ public boolean hasSuppressedBubbleWithKey(String key) {
+ return mSuppressedBubbles.values().stream().anyMatch(b -> b.getKey().equals(key));
+ }
+
+ /**
+ * Check if there are any bubbles suppressed with the given <code>LocusId</code>
+ */
+ public boolean isSuppressedWithLocusId(LocusId locusId) {
+ return mSuppressedBubbles.get(locusId) != null;
+ }
+
@Nullable
public BubbleViewProvider getSelectedBubble() {
return mSelectedBubble;
@@ -356,11 +371,11 @@ public class BubbleData {
boolean isSuppressed = mSuppressedBubbles.containsKey(locusId);
if (isSuppressed && (!bubble.isSuppressed() || !bubble.isSuppressable())) {
mSuppressedBubbles.remove(locusId);
- mStateChange.unsuppressedBubble = bubble;
+ doUnsuppress(bubble);
} else if (!isSuppressed && (bubble.isSuppressed()
|| bubble.isSuppressable() && mVisibleLocusIds.contains(locusId))) {
mSuppressedBubbles.put(locusId, bubble);
- mStateChange.suppressedBubble = bubble;
+ doSuppress(bubble);
}
}
dispatchPendingChanges();
@@ -532,16 +547,19 @@ public class BubbleData {
if (mPendingBubbles.containsKey(key)) {
mPendingBubbles.remove(key);
}
+
+ boolean shouldRemoveHiddenBubble = reason == Bubbles.DISMISS_NOTIF_CANCEL
+ || reason == Bubbles.DISMISS_GROUP_CANCELLED
+ || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE
+ || reason == Bubbles.DISMISS_BLOCKED
+ || reason == Bubbles.DISMISS_SHORTCUT_REMOVED
+ || reason == Bubbles.DISMISS_PACKAGE_REMOVED
+ || reason == Bubbles.DISMISS_USER_CHANGED;
+
int indexToRemove = indexForKey(key);
if (indexToRemove == -1) {
if (hasOverflowBubbleWithKey(key)
- && (reason == Bubbles.DISMISS_NOTIF_CANCEL
- || reason == Bubbles.DISMISS_GROUP_CANCELLED
- || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE
- || reason == Bubbles.DISMISS_BLOCKED
- || reason == Bubbles.DISMISS_SHORTCUT_REMOVED
- || reason == Bubbles.DISMISS_PACKAGE_REMOVED
- || reason == Bubbles.DISMISS_USER_CHANGED)) {
+ && shouldRemoveHiddenBubble) {
Bubble b = getOverflowBubbleWithKey(key);
if (DEBUG_BUBBLE_DATA) {
@@ -555,6 +573,17 @@ public class BubbleData {
mStateChange.bubbleRemoved(b, reason);
mStateChange.removedOverflowBubble = b;
}
+ if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
+ Bubble b = getSuppressedBubbleWithKey(key);
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Cancel suppressed bubble: " + b);
+ }
+ if (b != null) {
+ mSuppressedBubbles.remove(b.getLocusId());
+ b.stopInflation();
+ mStateChange.bubbleRemoved(b, reason);
+ }
+ }
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
@@ -562,17 +591,10 @@ public class BubbleData {
overflowBubble(reason, bubbleToRemove);
if (mBubbles.size() == 1) {
- if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
- // No more active bubbles but we have stuff in the overflow -- select that view
- // if we're already expanded or always showing.
- setShowingOverflow(true);
- setSelectedBubbleInternal(mOverflow);
- } else {
- setExpandedInternal(false);
- // Don't use setSelectedBubbleInternal because we don't want to trigger an
- // applyUpdate
- mSelectedBubble = null;
- }
+ setExpandedInternal(false);
+ // Don't use setSelectedBubbleInternal because we don't want to trigger an
+ // applyUpdate
+ mSelectedBubble = null;
}
if (indexToRemove < mBubbles.size() - 1) {
// Removing anything but the last bubble means positions will change.
@@ -586,19 +608,73 @@ public class BubbleData {
// Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
- // Move selection to the new bubble at the same position.
- int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
- BubbleViewProvider newSelected = mBubbles.get(newIndex);
- setSelectedBubbleInternal(newSelected);
+ setNewSelectedIndex(indexToRemove);
}
maybeSendDeleteIntent(reason, bubbleToRemove);
}
+ private void setNewSelectedIndex(int indexOfSelected) {
+ if (mBubbles.isEmpty()) {
+ Log.w(TAG, "Bubbles list empty when attempting to select index: " + indexOfSelected);
+ return;
+ }
+ // Move selection to the new bubble at the same position.
+ int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
+ }
+ BubbleViewProvider newSelected = mBubbles.get(newIndex);
+ setSelectedBubbleInternal(newSelected);
+ }
+
+ private void doSuppress(Bubble bubble) {
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "doSuppressed: " + bubble);
+ }
+ mStateChange.suppressedBubble = bubble;
+ bubble.setSuppressBubble(true);
+
+ int indexToRemove = mBubbles.indexOf(bubble);
+ // Order changes if we are not suppressing the last bubble
+ mStateChange.orderChanged = !(mBubbles.size() - 1 == indexToRemove);
+ mBubbles.remove(indexToRemove);
+
+ // Update selection if we suppressed the selected bubble
+ if (Objects.equals(mSelectedBubble, bubble)) {
+ if (mBubbles.isEmpty()) {
+ // Don't use setSelectedBubbleInternal because we don't want to trigger an
+ // applyUpdate
+ mSelectedBubble = null;
+ } else {
+ // Mark new first bubble as selected
+ setNewSelectedIndex(0);
+ }
+ }
+ }
+
+ private void doUnsuppress(Bubble bubble) {
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "doUnsuppressed: " + bubble);
+ }
+ bubble.setSuppressBubble(false);
+ mStateChange.unsuppressedBubble = bubble;
+ mBubbles.add(bubble);
+ if (mBubbles.size() > 1) {
+ // See where the bubble actually lands
+ repackAll();
+ mStateChange.orderChanged = true;
+ }
+ if (mBubbles.get(0) == bubble) {
+ // Unsuppressed bubble is sorted to first position. Mark it as the selected.
+ setNewSelectedIndex(0);
+ }
+ }
+
void overflowBubble(@DismissReason int reason, Bubble bubble) {
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
- || reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
+ || reason == Bubbles.DISMISS_USER_GESTURE
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
if (DEBUG_BUBBLE_DATA) {
@@ -626,7 +702,7 @@ public class BubbleData {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "dismissAll: reason=" + reason);
}
- if (mBubbles.isEmpty()) {
+ if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
return;
}
setExpandedInternal(false);
@@ -634,6 +710,10 @@ public class BubbleData {
while (!mBubbles.isEmpty()) {
doRemove(mBubbles.get(0).getKey(), reason);
}
+ while (!mSuppressedBubbles.isEmpty()) {
+ Bubble bubble = mSuppressedBubbles.removeAt(0);
+ doRemove(bubble.getKey(), reason);
+ }
dispatchPendingChanges();
}
@@ -642,11 +722,15 @@ public class BubbleData {
* and if there's a matching bubble for that locusId then the bubble may be hidden or shown
* depending on the visibility of the locusId.
*
- * @param taskId the taskId associated with the locusId visibility change.
+ * @param taskId the taskId associated with the locusId visibility change.
* @param locusId the locusId whose visibility has changed.
* @param visible whether the task with the locusId is visible or not.
*/
public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
+ }
+
Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
// Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
if (visible && (matchingBubble == null || matchingBubble.getTaskId() != taskId)) {
@@ -655,20 +739,22 @@ public class BubbleData {
mVisibleLocusIds.remove(locusId);
}
if (matchingBubble == null) {
- return;
+ // Check if there is a suppressed bubble for this LocusId
+ matchingBubble = mSuppressedBubbles.get(locusId);
+ if (matchingBubble == null) {
+ return;
+ }
}
boolean isAlreadySuppressed = mSuppressedBubbles.get(locusId) != null;
if (visible && !isAlreadySuppressed && matchingBubble.isSuppressable()
&& taskId != matchingBubble.getTaskId()) {
mSuppressedBubbles.put(locusId, matchingBubble);
- matchingBubble.setSuppressBubble(true);
- mStateChange.suppressedBubble = matchingBubble;
+ doSuppress(matchingBubble);
dispatchPendingChanges();
} else if (!visible) {
Bubble unsuppressedBubble = mSuppressedBubbles.remove(locusId);
if (unsuppressedBubble != null) {
- unsuppressedBubble.setSuppressBubble(false);
- mStateChange.unsuppressedBubble = unsuppressedBubble;
+ doUnsuppress(unsuppressedBubble);
}
dispatchPendingChanges();
}
@@ -727,14 +813,14 @@ public class BubbleData {
/**
* Logs the bubble UI event.
*
- * @param provider The bubble view provider that is being interacted on. Null value indicates
- * that the user interaction is not specific to one bubble.
- * @param action The user interaction enum
+ * @param provider The bubble view provider that is being interacted on. Null value indicates
+ * that the user interaction is not specific to one bubble.
+ * @param action The user interaction enum
* @param packageName SystemUI package
* @param bubbleCount Number of bubbles in the stack
* @param bubbleIndex Index of bubble in the stack
- * @param normalX Normalized x position of the stack
- * @param normalY Normalized y position of the stack
+ * @param normalX Normalized x position of the stack
+ * @param normalY Normalized y position of the stack
*/
void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
int bubbleCount, int bubbleIndex, float normalX, float normalY) {
@@ -876,6 +962,9 @@ public class BubbleData {
if (b == null) {
b = getOverflowBubbleWithKey(key);
}
+ if (b == null) {
+ b = getSuppressedBubbleWithKey(key);
+ }
return b;
}
@@ -953,6 +1042,23 @@ public class BubbleData {
return null;
}
+ /**
+ * Get a suppressed bubble with given notification <code>key</code>
+ *
+ * @param key notification key
+ * @return bubble that matches or null
+ */
+ @Nullable
+ @VisibleForTesting(visibility = PRIVATE)
+ public Bubble getSuppressedBubbleWithKey(String key) {
+ for (Bubble b : mSuppressedBubbles.values()) {
+ if (b.getKey().equals(key)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting(visibility = PRIVATE)
void setTimeSource(TimeSource timeSource) {
mTimeSource = timeSource;
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 a87aad4261a6..10ff2fb31b0d 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
@@ -60,6 +60,7 @@ import android.widget.LinearLayout;
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;
@@ -335,7 +336,7 @@ public class BubbleExpandedView extends LinearLayout {
mManageButton.setVisibility(GONE);
} else {
mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
- mController.getSyncTransactionQueue());
+ mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
mExpandedViewContainer.addView(mTaskView);
bringChildToFront(mTaskView);
@@ -386,13 +387,14 @@ public class BubbleExpandedView extends LinearLayout {
final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
- mCornerRadius = ta.getDimensionPixelSize(0, 0);
+ boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources());
+ mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
mBackgroundColorFloating = ta.getColor(1, Color.WHITE);
mExpandedViewContainer.setBackgroundColor(mBackgroundColorFloating);
ta.recycle();
- if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources())) {
+ if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
}
updatePointerView();
@@ -417,8 +419,9 @@ public class BubbleExpandedView extends LinearLayout {
mPointerView.setBackground(mCurrentPointer);
}
- private String getBubbleKey() {
- return mBubble != null ? mBubble.getKey() : "null";
+ @VisibleForTesting
+ public String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : mIsOverflow ? BubbleOverflow.KEY : null;
}
/**
@@ -689,6 +692,7 @@ public class BubbleExpandedView extends LinearLayout {
* @param bubblePosition the x position of the bubble if showing on top, the y position of
* the bubble if showing vertically.
* @param onLeft whether the stack was on the left side of the screen when expanded.
+ * @param animate whether the pointer should animate to this position.
*/
public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) {
// Pointer gets drawn in the padding
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
index b0e029fdc681..9d3bf34895d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -21,19 +21,12 @@ 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.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ShadowGenerator;
import com.android.wm.shell.R;
/**
@@ -44,12 +37,9 @@ import com.android.wm.shell.R;
@VisibleForTesting
public class BubbleIconFactory extends BaseIconFactory {
- private int mBadgeSize;
-
public BubbleIconFactory(Context context) {
super(context, context.getResources().getConfiguration().densityDpi,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
- mBadgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size);
}
/**
@@ -75,84 +65,4 @@ public class BubbleIconFactory extends BaseIconFactory {
return null;
}
}
-
- /**
- * 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) {
- ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize);
- Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize);
-
- if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
- userBadgedBitmap = Bitmap.createScaledBitmap(
- getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */
- userBadgedAppIcon.getIntrinsicWidth()),
- mBadgeSize, mBadgeSize, /* filter */ true);
- }
-
- if (isImportantConversation) {
- final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width);
- final int importantConversationColor = mContext.getResources().getColor(
- R.color.important_conversation, null);
- Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
- userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
- Canvas c = new Canvas(badgeAndRing);
-
- Paint ringPaint = new Paint();
- ringPaint.setStyle(Paint.Style.FILL);
- ringPaint.setColor(importantConversationColor);
- ringPaint.setAntiAlias(true);
- c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2, ringPaint);
-
- final int bitmapTop = (int) ringStrokeWidth;
- final int bitmapLeft = (int) ringStrokeWidth;
- final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth;
- final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth;
-
- Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth,
- bitmapHeight, /* filter */ true);
- c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null);
-
- shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c);
- return createIconBitmap(badgeAndRing);
- } else {
- Canvas c = new Canvas();
- c.setBitmap(userBadgedBitmap);
- shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
- return createIconBitmap(userBadgedBitmap);
- }
- }
-
- public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) {
- Drawable foreground = icon.getForeground();
- Drawable background = icon.getBackground();
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas();
- canvas.setBitmap(bitmap);
-
- // Clip canvas to circle.
- Path circlePath = new Path();
- circlePath.addCircle(/* x */ size / 2f,
- /* y */ size / 2f,
- /* radius */ size / 2f,
- Path.Direction.CW);
- canvas.clipPath(circlePath);
-
- // Draw background.
- background.setBounds(0, 0, size, size);
- background.draw(canvas);
-
- // Draw foreground. The foreground and background drawables are derived from adaptive icons
- // Some icon shapes fill more space than others, so adaptive icons are normalized to about
- // the same size. This size is smaller than the original bounds, so we estimate
- // the difference in this offset.
- int offset = size / 5;
- foreground.setBounds(-offset, -offset, size + offset, size + offset);
- foreground.draw(canvas);
-
- canvas.setBitmap(null);
- return bitmap;
- }
}
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 0c3a6b2dbd84..eb7929b8ca54 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
@@ -54,6 +54,7 @@ class BubbleOverflow(
/** Call before use and again if cleanUpExpandedState was called. */
fun initialize(controller: BubbleController) {
+ createExpandedView()
getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
}
@@ -66,7 +67,7 @@ class BubbleOverflow(
updateResources()
getExpandedView()?.applyThemeAttrs()
// Apply inset and new style to fresh icon drawable.
- getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
+ getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
updateBtnTheme()
}
@@ -89,20 +90,19 @@ class BubbleOverflow(
dotColor = colorAccent
val shapeColor = res.getColor(android.R.color.system_accent1_1000)
- overflowBtn?.drawable?.setTint(shapeColor)
+ overflowBtn?.iconDrawable?.setTint(shapeColor)
val iconFactory = BubbleIconFactory(context)
// Update bitmap
- val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
- bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
- ColorDrawable(colorAccent), fg),
- null /* user */, true /* shrinkNonAdaptiveIcons */).icon
+ val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
+ bitmap = iconFactory.createBadgedIconBitmap(
+ AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
// Update dot path
dotPath = PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask))
- val scale = iconFactory.normalizer.getScale(iconView!!.drawable,
+ val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable,
null /* outBounds */, null /* path */, null /* outMaskShape */)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
@@ -124,13 +124,15 @@ class BubbleOverflow(
overflowBtn?.updateDotVisibility(true /* animate */)
}
+ fun createExpandedView(): BubbleExpandedView? {
+ expandedView = inflater.inflate(R.layout.bubble_expanded_view,
+ null /* root */, false /* attachToRoot */) as BubbleExpandedView
+ expandedView?.applyThemeAttrs()
+ updateResources()
+ return expandedView
+ }
+
override fun getExpandedView(): BubbleExpandedView? {
- if (expandedView == null) {
- expandedView = inflater.inflate(R.layout.bubble_expanded_view,
- null /* root */, false /* attachToRoot */) as BubbleExpandedView
- expandedView?.applyThemeAttrs()
- updateResources()
- }
return expandedView
}
@@ -142,6 +144,10 @@ class BubbleOverflow(
return null
}
+ override fun getRawAppBadge(): Bitmap? {
+ return null
+ }
+
override fun getBubbleIcon(): Bitmap {
return bitmap
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 5e9d97f23c57..fcd0ed7308ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -20,11 +20,13 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -58,6 +60,8 @@ public class BubbleOverflowContainerView extends LinearLayout {
private TextView mEmptyStateTitle;
private TextView mEmptyStateSubtitle;
private ImageView mEmptyStateImage;
+ private int mHorizontalMargin;
+ private int mVerticalMargin;
private BubbleController mController;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
@@ -77,12 +81,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
super(context, columns);
}
-// @Override
-// public boolean canScrollVertically() {
-// // TODO (b/162006693): this should be based on items in the list & available height
-// return true;
-// }
-
@Override
public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
@@ -98,6 +96,17 @@ public class BubbleOverflowContainerView extends LinearLayout {
}
}
+ private class OverflowItemDecoration extends RecyclerView.ItemDecoration {
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+ @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ outRect.left = mHorizontalMargin;
+ outRect.top = mVerticalMargin;
+ outRect.right = mHorizontalMargin;
+ outRect.bottom = mVerticalMargin;
+ }
+ }
+
public BubbleOverflowContainerView(Context context) {
this(context, null);
}
@@ -161,6 +170,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
new OverflowGridLayoutManager(getContext(), columns));
+ if (mRecyclerView.getItemDecorationCount() == 0) {
+ mRecyclerView.addItemDecoration(new OverflowItemDecoration());
+ }
mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
mController::promoteBubbleFromOverflow,
mController.getPositioner());
@@ -188,6 +200,13 @@ public class BubbleOverflowContainerView extends LinearLayout {
final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
+ mHorizontalMargin = res.getDimensionPixelSize(
+ R.dimen.bubble_overflow_item_padding_horizontal);
+ mVerticalMargin = res.getDimensionPixelSize(R.dimen.bubble_overflow_item_padding_vertical);
+ if (mRecyclerView != null) {
+ mRecyclerView.invalidateItemDecorations();
+ }
+
mEmptyStateImage.setImageDrawable(isNightMode
? res.getDrawable(R.drawable.bubble_ic_empty_overflow_dark)
: res.getDrawable(R.drawable.bubble_ic_empty_overflow_light));
@@ -277,8 +296,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
}
@Override
- public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
+ public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Set layout for overflow bubble view.
LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
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 127d5a8a9966..97e5ee3a35d3 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
@@ -20,6 +20,7 @@ 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;
import android.graphics.Insets;
import android.graphics.PointF;
@@ -66,7 +67,11 @@ public class BubblePositioner {
/** 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. **/
- public static final float EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT = 0.72f;
+ 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. **/
+ 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. **/
+ private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
private Context mContext;
private WindowManager mWindowManager;
@@ -76,6 +81,7 @@ public class BubblePositioner {
private boolean mImeVisible;
private int mImeHeight;
private boolean mIsLargeScreen;
+ private boolean mIsSmallTablet;
private Rect mPositionRect;
private int mDefaultMaxBubbles;
@@ -85,7 +91,8 @@ public class BubblePositioner {
private int mExpandedViewMinHeight;
private int mExpandedViewLargeScreenWidth;
- private int mExpandedViewLargeScreenInset;
+ private int mExpandedViewLargeScreenInsetClosestEdge;
+ private int mExpandedViewLargeScreenInsetFurthestEdge;
private int mOverflowWidth;
private int mExpandedViewPadding;
@@ -112,10 +119,6 @@ public class BubblePositioner {
update();
}
- public void setRotation(int rotation) {
- mRotation = rotation;
- }
-
/**
* Available space and inset information. Call this when config changes
* occur or when added to a window.
@@ -130,17 +133,26 @@ public class BubblePositioner {
| WindowInsets.Type.statusBars()
| WindowInsets.Type.displayCutout());
- mIsLargeScreen = mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ final Rect bounds = windowMetrics.getBounds();
+ Configuration config = mContext.getResources().getConfiguration();
+ mIsLargeScreen = config.smallestScreenWidthDp >= 600;
+ if (mIsLargeScreen) {
+ float largestEdgeDp = Math.max(config.screenWidthDp, config.screenHeightDp);
+ mIsSmallTablet = largestEdgeDp < 960;
+ } else {
+ mIsSmallTablet = false;
+ }
if (BubbleDebugConfig.DEBUG_POSITIONER) {
Log.w(TAG, "update positioner:"
+ " rotation: " + mRotation
+ " insets: " + insets
+ " isLargeScreen: " + mIsLargeScreen
- + " bounds: " + windowMetrics.getBounds()
+ + " isSmallTablet: " + mIsSmallTablet
+ + " bounds: " + bounds
+ " showingInTaskbar: " + mShowingInTaskbar);
}
- updateInternal(mRotation, insets, windowMetrics.getBounds());
+ updateInternal(mRotation, insets, bounds);
}
/**
@@ -175,15 +187,32 @@ 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);
- mExpandedViewLargeScreenWidth = (int) (bounds.width()
- * EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT);
- mExpandedViewLargeScreenInset = mIsLargeScreen
- ? (bounds.width() - mExpandedViewLargeScreenWidth) / 2
- : mExpandedViewPadding;
- mOverflowWidth = mIsLargeScreen
- ? mExpandedViewLargeScreenWidth
- : res.getDimensionPixelSize(
- R.dimen.bubble_expanded_view_phone_landscape_overflow_width);
+ if (mIsSmallTablet) {
+ mExpandedViewLargeScreenWidth = (int) (bounds.width()
+ * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
+ } else {
+ mExpandedViewLargeScreenWidth = isLandscape()
+ ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
+ : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+ }
+ if (mIsLargeScreen) {
+ if (isLandscape() && !mIsSmallTablet) {
+ mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding);
+ mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
+ - mExpandedViewLargeScreenInsetClosestEdge
+ - mExpandedViewLargeScreenWidth;
+ } else {
+ final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+ mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+ mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+ }
+ } else {
+ mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
+ mExpandedViewLargeScreenInsetFurthestEdge = mExpandedViewPadding;
+ }
+
+ mOverflowWidth = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_overflow_width);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
@@ -273,7 +302,8 @@ public class BubblePositioner {
/** @return whether the device is in landscape orientation. */
public boolean isLandscape() {
- return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+ return mContext.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
/** @return whether the screen is considered large. */
@@ -315,6 +345,15 @@ public class BubblePositioner {
mImeHeight = height;
}
+ private int getExpandedViewLargeScreenInsetFurthestEdge(boolean isOverflow) {
+ if (isOverflow && mIsLargeScreen) {
+ return mScreenRect.width()
+ - mExpandedViewLargeScreenInsetClosestEdge
+ - mOverflowWidth;
+ }
+ return mExpandedViewLargeScreenInsetFurthestEdge;
+ }
+
/**
* Calculates the padding for the bubble expanded view.
*
@@ -329,16 +368,21 @@ public class BubblePositioner {
*/
public int[] getExpandedViewContainerPadding(boolean onLeft, boolean isOverflow) {
final int pointerTotalHeight = mPointerHeight - mPointerOverlap;
+ final int expandedViewLargeScreenInsetFurthestEdge =
+ getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
if (mIsLargeScreen) {
+ // Note:
+ // If we're in portrait OR if we're a small tablet, then the two insets values will
+ // be equal. If we're landscape and a large tablet, the two values will be different.
// [left, top, right, bottom]
mPaddings[0] = onLeft
- ? mExpandedViewLargeScreenInset - pointerTotalHeight
- : mExpandedViewLargeScreenInset;
+ ? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
+ : expandedViewLargeScreenInsetFurthestEdge;
mPaddings[1] = 0;
mPaddings[2] = onLeft
- ? mExpandedViewLargeScreenInset
- : mExpandedViewLargeScreenInset - pointerTotalHeight;
- // Overflow doesn't show manage button / get padding from it so add padding here for it
+ ? expandedViewLargeScreenInsetFurthestEdge
+ : mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
+ // Overflow doesn't show manage button / get padding from it so add padding here
mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
return mPaddings;
} else {
@@ -496,12 +540,13 @@ public class BubblePositioner {
float x;
float y;
if (showBubblesVertically()) {
+ int inset = mExpandedViewLargeScreenInsetClosestEdge;
y = rowStart + positionInRow;
int left = mIsLargeScreen
- ? mExpandedViewLargeScreenInset - mExpandedViewPadding - mBubbleSize
+ ? inset - mExpandedViewPadding - mBubbleSize
: mPositionRect.left;
int right = mIsLargeScreen
- ? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding
+ ? mPositionRect.right - inset + mExpandedViewPadding
: mPositionRect.right - mBubbleSize;
x = state.onLeft
? left
@@ -534,11 +579,10 @@ public class BubblePositioner {
// Showing vertically: might need to translate the bubbles above the IME.
// Subtract spacing here to provide a margin between top of IME and bottom of bubble row.
- final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+ final float bottomHeight = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+ final float bottomInset = mScreenRect.bottom - bottomHeight;
final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
- final float centerPosition = showBubblesVertically()
- ? mPositionRect.centerY()
- : mPositionRect.centerX();
+ final float centerPosition = mPositionRect.centerY();
final float rowBottom = centerPosition + (expandedStackSize / 2f);
final float rowTop = centerPosition - (expandedStackSize / 2f);
float rowTopForIme = rowTop;
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 14433c233273..331941103028 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
@@ -70,6 +70,7 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -167,26 +168,27 @@ public class BubbleStackView extends FrameLayout
private static final SurfaceSynchronizer DEFAULT_SURFACE_SYNCHRONIZER =
new SurfaceSynchronizer() {
- @Override
- public void syncSurfaceAndRun(Runnable callback) {
- Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
- // Just wait 2 frames. There is no guarantee, but this is usually enough time that
- // the requested change is reflected on the screen.
- // TODO: Once SurfaceFlinger provide APIs to sync the state of {@code View} and
- // surfaces, rewrite this logic with them.
- private int mFrameWait = 2;
-
@Override
- public void doFrame(long frameTimeNanos) {
- if (--mFrameWait > 0) {
- Choreographer.getInstance().postFrameCallback(this);
- } else {
- callback.run();
- }
+ public void syncSurfaceAndRun(Runnable callback) {
+ Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
+ // Just wait 2 frames. There is no guarantee, but this is usually enough
+ // time that the requested change is reflected on the screen.
+ // TODO: Once SurfaceFlinger provide APIs to sync the state of
+ // {@code View} and surfaces, rewrite this logic with them.
+ private int mFrameWait = 2;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if (--mFrameWait > 0) {
+ Choreographer.getInstance().postFrameCallback(this);
+ } else {
+ callback.run();
+ }
+ }
+ };
+ Choreographer.getInstance().postFrameCallback(frameCallback);
}
- });
- }
- };
+ };
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private StackViewState mStackViewState = new StackViewState();
@@ -780,12 +782,12 @@ public class BubbleStackView extends FrameLayout
mPositioner = mBubbleController.getPositioner();
final TypedArray ta = mContext.obtainStyledAttributes(
- new int[] {android.R.attr.dialogCornerRadius});
+ new int[]{android.R.attr.dialogCornerRadius});
mCornerRadius = ta.getDimensionPixelSize(0, 0);
ta.recycle();
final Runnable onBubbleAnimatedOut = () -> {
- if (getBubbleCount() == 0 && !mBubbleData.isShowingOverflow()) {
+ if (getBubbleCount() == 0) {
mBubbleController.onAllBubblesAnimatedOut();
}
};
@@ -822,7 +824,9 @@ public class BubbleStackView extends FrameLayout
mAnimatingOutSurfaceView = new SurfaceView(getContext());
mAnimatingOutSurfaceView.setUseAlpha();
mAnimatingOutSurfaceView.setZOrderOnTop(true);
- mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius);
+ boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources());
+ mAnimatingOutSurfaceView.setCornerRadius(supportsRoundedCorners ? mCornerRadius : 0);
mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
mAnimatingOutSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
@@ -895,7 +899,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mBubbleOverflow.updateResources();
- if (mRelativeStackPositionBeforeRotation != null) {
+ if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) {
mStackAnimationController.setStackPosition(
mRelativeStackPositionBeforeRotation);
mRelativeStackPositionBeforeRotation = null;
@@ -939,7 +943,7 @@ public class BubbleStackView extends FrameLayout
});
// If the stack itself is clicked, it means none of its touchable views (bubbles, flyouts,
- // TaskView, etc.) were touched. Collapse the stack if it's expanded.
+ // TaskView, etc.) were touched. Collapse the stack if it's expanded.
setOnClickListener(view -> {
if (mShowingManage) {
showManageMenu(false /* show */);
@@ -1045,10 +1049,17 @@ public class BubbleStackView extends FrameLayout
private final Runnable mAnimateTemporarilyInvisibleImmediate = () -> {
if (mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE) {
+ // To calculate a distance, bubble stack needs to be moved to become hidden,
+ // we need to take into account that the bubble stack is positioned on the edge
+ // of the available screen rect, which can be offset by system bars and cutouts.
if (mStackAnimationController.isStackOnLeftSide()) {
- animate().translationX(-mBubbleSize).start();
+ int availableRectOffsetX =
+ mPositioner.getAvailableRect().left - mPositioner.getScreenRect().left;
+ animate().translationX(-(mBubbleSize + availableRectOffsetX)).start();
} else {
- animate().translationX(mBubbleSize).start();
+ int availableRectOffsetX =
+ mPositioner.getAvailableRect().right - mPositioner.getScreenRect().right;
+ animate().translationX(mBubbleSize - availableRectOffsetX).start();
}
} else {
animate().translationX(0).start();
@@ -1128,6 +1139,7 @@ public class BubbleStackView extends FrameLayout
// The menu itself should respect locale direction so the icons are on the correct side.
mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
addView(mManageMenu);
+ updateManageButtonListener();
}
/**
@@ -1189,6 +1201,8 @@ public class BubbleStackView extends FrameLayout
addView(mStackEduView);
}
mBubbleContainer.bringToFront();
+ // Ensure the stack is in the correct spot
+ mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
return mStackEduView.show(mPositioner.getDefaultStartPosition());
}
@@ -1203,6 +1217,8 @@ public class BubbleStackView extends FrameLayout
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
mBubbleContainer.bringToFront(); // Stack appears on top of the stack education
+ // Ensure the stack is in the correct spot
+ mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
mStackEduView.show(mPositioner.getDefaultStartPosition());
}
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
@@ -1233,7 +1249,7 @@ public class BubbleStackView extends FrameLayout
b.getExpandedView().updateFontSize();
}
}
- if (mBubbleOverflow != null) {
+ if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
mBubbleOverflow.getExpandedView().updateFontSize();
}
}
@@ -1300,7 +1316,6 @@ public class BubbleStackView extends FrameLayout
/** Respond to the display size change by recalculating view size and location. */
public void onDisplaySizeChanged() {
updateOverflow();
- setUpManageMenu();
setUpFlyout();
setUpDismissView();
updateUserEdu();
@@ -1320,13 +1335,16 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mDismissView.updateResources();
mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
- mStackAnimationController.setStackPosition(
- new RelativeStackPosition(
- mPositioner.getRestingPosition(),
- mStackAnimationController.getAllowableStackPositionRegion()));
+ if (!isStackEduShowing()) {
+ mStackAnimationController.setStackPosition(
+ new RelativeStackPosition(
+ mPositioner.getRestingPosition(),
+ mStackAnimationController.getAllowableStackPositionRegion()));
+ }
if (mIsExpanded) {
updateExpandedView();
}
+ setUpManageMenu();
}
@Override
@@ -1482,6 +1500,69 @@ public class BubbleStackView extends FrameLayout
}
}
+ /**
+ * Update bubbles' icon views accessibility states.
+ */
+ public void updateBubblesAcessibillityStates() {
+ for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
+ Bubble prevBubble = i > 0 ? mBubbleData.getBubbles().get(i - 1) : null;
+ Bubble bubble = mBubbleData.getBubbles().get(i);
+
+ View bubbleIconView = bubble.getIconView();
+ if (bubbleIconView == null) {
+ continue;
+ }
+
+ if (mIsExpanded) {
+ // when stack is expanded
+ // all bubbles are important for accessibility
+ bubbleIconView
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+ View prevBubbleIconView = prevBubble != null ? prevBubble.getIconView() : null;
+
+ if (prevBubbleIconView != null) {
+ bubbleIconView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View v,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+ info.setTraversalAfter(prevBubbleIconView);
+ }
+ });
+ }
+ } else {
+ // when stack is collapsed, only the top bubble is important for accessibility,
+ bubbleIconView.setImportantForAccessibility(
+ i == 0 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES :
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+ }
+
+ if (mIsExpanded) {
+ // make the overflow bubble last in the accessibility traversal order
+
+ View bubbleOverflowIconView =
+ mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null;
+ if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) {
+ Bubble lastBubble =
+ mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1);
+ View lastBubbleIconView = lastBubble.getIconView();
+ if (lastBubbleIconView != null) {
+ bubbleOverflowIconView.setAccessibilityDelegate(
+ new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View v,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+ info.setTraversalAfter(lastBubbleIconView);
+ }
+ });
+ }
+ }
+ }
+ }
+
private void updateSystemGestureExcludeRects() {
// Exclude the region occupied by the first BubbleView in the stack
Rect excludeZone = mSystemGestureExclusionRects.get(0);
@@ -1541,7 +1622,9 @@ public class BubbleStackView extends FrameLayout
Log.d(TAG, "addBubble: " + bubble);
}
- if (getBubbleCount() == 0 && shouldShowStackEdu()) {
+ final boolean firstBubble = getBubbleCount() == 0;
+
+ if (firstBubble && shouldShowStackEdu()) {
// Override the default stack position if we're showing user education.
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
}
@@ -1554,7 +1637,7 @@ public class BubbleStackView extends FrameLayout
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
- if (getBubbleCount() == 0) {
+ if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
// Set the dot position to the opposite of the side the stack is resting on, since the stack
@@ -1584,13 +1667,21 @@ public class BubbleStackView extends FrameLayout
} else {
bubble.cleanupViews();
}
- updatePointerPosition(false /* forIme */);
updateExpandedView();
+ if (getBubbleCount() == 0 && !isExpanded()) {
+ // This is the last bubble and the stack is collapsed
+ updateStackPosition();
+ }
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
}
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ // If a bubble is suppressed, it is not attached to the container. Clean it up.
+ if (bubble.isSuppressed()) {
+ bubble.cleanupViews();
+ } else {
+ Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ }
}
private void updateOverflowVisibility() {
@@ -1776,11 +1867,30 @@ public class BubbleStackView extends FrameLayout
}
}
- void setBubbleVisibility(Bubble b, boolean visible) {
- if (b.getIconView() != null) {
- b.getIconView().setVisibility(visible ? VISIBLE : GONE);
+ void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
+ Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
+ }
+ if (suppressed) {
+ int index = getBubbleIndex(bubble);
+ mBubbleContainer.removeViewAt(index);
+ updateExpandedView();
+ } else {
+ if (bubble.getIconView() == null) {
+ return;
+ }
+ if (bubble.getIconView().getParent() != null) {
+ Log.e(TAG, "Bubble is already added to parent. Can't unsuppress: " + bubble);
+ return;
+ }
+ int index = mBubbleData.getBubbles().indexOf(bubble);
+ // Add the view back to the correct position
+ mBubbleContainer.addView(bubble.getIconView(), index,
+ new LayoutParams(mPositioner.getBubbleSize(),
+ mPositioner.getBubbleSize()));
+ updateBubbleShadows(false /* showForAllBubbles */);
+ requestUpdate();
}
- // TODO(b/181166384): Animate in / out & handle adjusting how the bubbles overlap
}
/**
@@ -2125,7 +2235,7 @@ public class BubbleStackView extends FrameLayout
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
.spring(DynamicAnimation.TRANSLATION_Y,
mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize,
- mTranslateSpringConfig)
+ mTranslateSpringConfig)
.start();
}
@@ -2614,11 +2724,13 @@ public class BubbleStackView extends FrameLayout
// If available, update the manage menu's settings option with the expanded bubble's app
// name and icon.
- if (show && mBubbleData.hasBubbleInStackWithKey(mExpandedBubble.getKey())) {
+ if (show) {
final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
- mManageSettingsIcon.setImageBitmap(bubble.getAppBadge());
- mManageSettingsText.setText(getResources().getString(
- R.string.bubbles_app_settings, bubble.getAppName()));
+ if (bubble != null) {
+ mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
+ mManageSettingsText.setText(getResources().getString(
+ R.string.bubbles_app_settings, bubble.getAppName()));
+ }
}
if (mExpandedBubble.getExpandedView().getTaskView() != null) {
@@ -2697,7 +2809,14 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setVisibility(View.INVISIBLE);
mExpandedViewContainer.setAlpha(0f);
mExpandedViewContainer.addView(bev);
- bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
+
+ postDelayed(() -> {
+ // Set the Manage button click handler from postDelayed. This appears to resolve
+ // a race condition with adding the BubbleExpandedView view to the expanded view
+ // container. Due to the race condition the click handler sometimes is not set up
+ // correctly and is never called.
+ updateManageButtonListener();
+ }, 0);
if (!mIsExpansionAnimating) {
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -2707,6 +2826,16 @@ public class BubbleStackView extends FrameLayout
}
}
+ private void updateManageButtonListener() {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ bev.setManageClickListener((view) -> {
+ showManageMenu(true /* show */);
+ });
+ }
+ }
+
/**
* Requests a snapshot from the currently expanded bubble's TaskView and displays it in a
* SurfaceView. This allows us to load a newly expanded bubble's Activity into the TaskView,
@@ -2972,14 +3101,14 @@ public class BubbleStackView extends FrameLayout
* Logs the bubble UI event.
*
* @param provider the bubble view provider that is being interacted on. Null value indicates
- * that the user interaction is not specific to one bubble.
- * @param action the user interaction enum.
+ * that the user interaction is not specific to one bubble.
+ * @param action the user interaction enum.
*/
private void logBubbleEvent(@Nullable BubbleViewProvider provider, int action) {
final String packageName =
(provider != null && provider instanceof Bubble)
- ? ((Bubble) provider).getPackageName()
- : "null";
+ ? ((Bubble) provider).getPackageName()
+ : "null";
mBubbleData.logBubbleEvent(provider,
action,
packageName,
@@ -3012,6 +3141,16 @@ public class BubbleStackView extends FrameLayout
}
/**
+ * Handles vertical offset changes, e.g. when one handed mode is switched on/off.
+ *
+ * @param offset new vertical offset.
+ */
+ void onVerticalOffsetChanged(int offset) {
+ // adjust dismiss view vertical position, so that it is still visible to the user
+ mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
+ }
+
+ /**
* Holds some commonly queried information about the stack.
*/
public static class StackViewState {
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 932f879caef8..69762c9bc06a 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
@@ -71,6 +71,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
private WeakReference<BubbleController> mController;
private WeakReference<BubbleStackView> mStackView;
private BubbleIconFactory mIconFactory;
+ private BubbleBadgeIconFactory mBadgeIconFactory;
private boolean mSkipInflation;
private Callback mCallback;
private Executor mMainExecutor;
@@ -84,6 +85,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
BubbleController controller,
BubbleStackView stackView,
BubbleIconFactory factory,
+ BubbleBadgeIconFactory badgeFactory,
boolean skipInflation,
Callback c,
Executor mainExecutor) {
@@ -92,6 +94,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
mController = new WeakReference<>(controller);
mStackView = new WeakReference<>(stackView);
mIconFactory = factory;
+ mBadgeIconFactory = badgeFactory;
mSkipInflation = skipInflation;
mCallback = c;
mMainExecutor = mainExecutor;
@@ -100,7 +103,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
- mIconFactory, mBubble, mSkipInflation);
+ mIconFactory, mBadgeIconFactory, mBubble, mSkipInflation);
}
@Override
@@ -127,6 +130,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
String appName;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
+ Bitmap mRawBadgeBitmap;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
@@ -134,7 +138,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@VisibleForTesting
@Nullable
public static BubbleViewInfo populate(Context c, BubbleController controller,
- BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
+ BubbleStackView stackView, BubbleIconFactory iconFactory,
+ BubbleBadgeIconFactory badgeIconFactory, Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
@@ -186,12 +191,12 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
bubbleDrawable = appIcon;
}
- BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
+ BitmapInfo badgeBitmapInfo = badgeIconFactory.getBadgeBitmap(badgedIcon,
b.isImportantConversation());
info.badgeBitmap = badgeBitmapInfo.icon;
- info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
- null /* user */,
- true /* shrinkNonAdaptiveIcons */).icon;
+ // Raw badge bitmap never includes the important conversation ring
+ info.mRawBadgeBitmap = badgeIconFactory.getBadgeBitmap(badgedIcon, false).icon;
+ info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon;
// 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 7e552826e94a..3f6d41bb2b68 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
@@ -43,6 +43,9 @@ public interface BubbleViewProvider {
/** App badge drawable to draw above bubble icon. */
@Nullable Bitmap getAppBadge();
+ /** Base app badge drawable without any markings. */
+ @Nullable Bitmap getRawAppBadge();
+
/** Path of normalized bubble icon to draw dot on. */
Path getDotPath();
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 c82249b8a369..af403d23d69c 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
@@ -21,9 +21,12 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.app.NotificationChannel;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -191,10 +194,26 @@ public interface Bubbles {
* @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
* bubble up
*/
- void onRankingUpdated(RankingMap rankingMap,
+ void onRankingUpdated(
+ RankingMap rankingMap,
HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);
/**
+ * Called when a notification channel is modified, in response to
+ * {@link NotificationListenerService#onNotificationChannelModified}.
+ *
+ * @param pkg the package the notification channel belongs to.
+ * @param user the user the notification channel belongs to.
+ * @param channel the channel being modified.
+ * @param modificationType the type of modification that occurred to the channel.
+ */
+ void onNotificationChannelModified(
+ String pkg,
+ UserHandle user,
+ NotificationChannel channel,
+ int modificationType);
+
+ /**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
index 74672a336161..063dac3d4109 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
@@ -16,11 +16,16 @@
package com.android.wm.shell.bubbles
+import android.animation.ObjectAnimator
import android.content.Context
-import android.graphics.drawable.TransitionDrawable
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.IntProperty
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
@@ -28,8 +33,6 @@ import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
import com.android.wm.shell.common.DismissCircleView
-import android.view.WindowInsets
-import android.view.WindowManager
/*
* View that handles interactions between DismissCircleView and BubbleStackView.
@@ -41,9 +44,21 @@ class DismissView(context: Context) : FrameLayout(context) {
private val animator = PhysicsAnimator.getInstance(circle)
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
- private val DISMISS_SCRIM_FADE_MS = 200
+ private val DISMISS_SCRIM_FADE_MS = 200L
private var wm: WindowManager =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private var gradientDrawable = createGradient()
+
+ private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+ object : IntProperty<GradientDrawable>("alpha") {
+ override fun setValue(d: GradientDrawable, percent: Int) {
+ d.alpha = percent
+ }
+ override fun get(d: GradientDrawable): Int {
+ return d.alpha
+ }
+ }
+
init {
setLayoutParams(LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -53,8 +68,7 @@ class DismissView(context: Context) : FrameLayout(context) {
setClipToPadding(false)
setClipChildren(false)
setVisibility(View.INVISIBLE)
- setBackgroundResource(
- R.drawable.floating_dismiss_gradient_transition)
+ setBackgroundDrawable(gradientDrawable)
val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
addView(circle, LayoutParams(targetSize, targetSize,
@@ -71,7 +85,11 @@ class DismissView(context: Context) : FrameLayout(context) {
if (isShowing) return
isShowing = true
setVisibility(View.VISIBLE)
- (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS)
+ val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+ gradientDrawable.alpha, 255)
+ alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+ alphaAnim.start()
+
animator.cancel()
animator
.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
@@ -85,7 +103,10 @@ class DismissView(context: Context) : FrameLayout(context) {
fun hide() {
if (!isShowing) return
isShowing = false
- (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS)
+ val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+ gradientDrawable.alpha, 0)
+ alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+ alphaAnim.start()
animator
.spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
spring)
@@ -93,6 +114,13 @@ class DismissView(context: Context) : FrameLayout(context) {
.start()
}
+ /**
+ * Cancels the animator for the dismiss target.
+ */
+ fun cancelAnimators() {
+ animator.cancel()
+ }
+
fun updateResources() {
updatePadding()
layoutParams.height = resources.getDimensionPixelSize(
@@ -104,6 +132,20 @@ class DismissView(context: Context) : FrameLayout(context) {
circle.requestLayout()
}
+ private fun createGradient(): GradientDrawable {
+ val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
+ val alpha = 0.7f * 255
+ val gradientColorWithAlpha = Color.argb(alpha.toInt(),
+ Color.red(gradientColor),
+ Color.green(gradientColor),
+ Color.blue(gradientColor))
+ val gd = GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP,
+ intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
+ gd.setAlpha(0)
+ return gd
+ }
+
private fun updatePadding() {
val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
val navInset = insets.getInsetsIgnoringVisibility(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index eb4737ac6c63..c09d1e0d189c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -101,9 +101,8 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi
bubbleExpandedView = expandedView
expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
- layoutParams.width = if (positioner.isLargeScreen)
- context.resources.getDimensionPixelSize(
- R.dimen.bubbles_user_education_width_large_screen)
+ layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
+ context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
else ViewGroup.LayoutParams.MATCH_PARENT
alpha = 0f
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 3846de73842d..1ff4be887fb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -122,28 +122,29 @@ class StackEducationView constructor(
* If necessary, shows the user education view for the bubble stack. This appears the first
* time a user taps on a bubble.
*
- * @return true if user education was shown, false otherwise.
+ * @return true if user education was shown and wasn't showing before, false otherwise.
*/
fun show(stackPosition: PointF): Boolean {
isHiding = false
if (visibility == VISIBLE) return false
controller.updateWindowFlagsForBackpress(true /* interceptBack */)
- layoutParams.width = if (positioner.isLargeScreen)
- context.resources.getDimensionPixelSize(
- R.dimen.bubbles_user_education_width_large_screen)
+ layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
+ context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
else ViewGroup.LayoutParams.MATCH_PARENT
+ val stackPadding = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_user_education_stack_padding)
setAlpha(0f)
setVisibility(View.VISIBLE)
post {
requestFocus()
with(view) {
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
- setPadding(positioner.bubbleSize + paddingRight, paddingTop, paddingRight,
+ setPadding(positioner.bubbleSize + stackPadding, paddingTop, paddingRight,
paddingBottom)
} else {
- setPadding(paddingLeft, paddingTop, positioner.bubbleSize + paddingLeft,
+ setPadding(paddingLeft, paddingTop, positioner.bubbleSize + stackPadding,
paddingBottom)
}
translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index 4ec2c8d4d362..55052e614458 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -364,6 +364,11 @@ public class PhysicsAnimationLayout extends FrameLayout {
final int oldIndex = indexOfChild(view);
super.removeView(view);
+ if (view.getParent() != null) {
+ // View still has a parent. This could have been added as a transient view.
+ // Remove it from transient views.
+ super.removeTransientView(view);
+ }
addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */);
if (mController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 60b64333114e..3ba056a6b4a2 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
@@ -750,6 +750,12 @@ public class StackAnimationController extends
// Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
// to the back of the stack, it'll be largely invisible so don't bother animating it in.
animateInBubble(child, index);
+ } else {
+ // We are not animating the bubble in. Make sure it has the right alpha and scale values
+ // in case this view was previously removed and is being re-added.
+ child.setAlpha(1f);
+ child.setScaleX(1f);
+ child.setScaleY(1f);
}
}
@@ -785,23 +791,24 @@ public class StackAnimationController extends
}
};
+ boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
final int oldIndex = mLayout.indexOfChild(view);
- animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
+ if (!swapped) {
+ // All bubbles were at the right position. Make sure badges and z order is correct.
+ updateAllIcons.run();
}
}
- private void animateSwap(View view, int oldIndex, int newIndex,
+ private boolean animateSwap(View view, int oldIndex, int newIndex,
Runnable updateAllIcons, Runnable finishReorder) {
if (newIndex == oldIndex) {
- // Add new bubble to index 0; move existing bubbles down
- updateBadgesAndZOrder(view, newIndex);
- if (newIndex == 0) {
- animateInBubble(view, newIndex);
- } else {
- moveToFinalIndex(view, newIndex, finishReorder);
- }
+ // View order did not change. Make sure position is correct.
+ moveToFinalIndex(view, newIndex, finishReorder);
+ return false;
} else {
// Reorder existing bubbles
if (newIndex == 0) {
@@ -809,6 +816,7 @@ public class StackAnimationController extends
} else {
moveToFinalIndex(view, newIndex, finishReorder);
}
+ return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index ffda1f92ec90..c32733d4f73c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -27,7 +27,6 @@ import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -71,12 +70,18 @@ public class DisplayChangeController {
mRotationListener.remove(listener);
}
+ /** Query all listeners for changes that should happen on rotation. */
+ public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId,
+ final int fromRotation, final int toRotation) {
+ for (OnDisplayChangingListener c : mRotationListener) {
+ c.onRotateDisplay(displayId, fromRotation, toRotation, outWct);
+ }
+ }
+
private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
IDisplayWindowRotationCallback callback) {
WindowContainerTransaction t = new WindowContainerTransaction();
- for (OnDisplayChangingListener c : mRotationListener) {
- c.onRotateDisplay(displayId, fromRotation, toRotation, t);
- }
+ dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation);
try {
callback.continueRotateDisplay(toRotation, t);
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index a1fb658ccb9d..4ba32e93fb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -19,8 +19,10 @@ package com.android.wm.shell.common;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -34,6 +36,8 @@ import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingList
import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
/**
* This module deals with display rotations coming from WM. When WM starts a rotation: after it has
@@ -76,6 +80,11 @@ public class DisplayController {
}
}
+ /** Get the DisplayChangeController. */
+ public DisplayChangeController getChangeController() {
+ return mChangeController;
+ }
+
/**
* Gets a display by id from DisplayManager.
*/
@@ -101,6 +110,14 @@ public class DisplayController {
}
/**
+ * Get the InsetsState of a display.
+ */
+ public InsetsState getInsetsState(int displayId) {
+ final DisplayRecord r = mDisplays.get(displayId);
+ return r != null ? r.mInsetsState : null;
+ }
+
+ /**
* Updates the insets for a given display.
*/
public void updateDisplayInsets(int displayId, InsetsState state) {
@@ -238,6 +255,21 @@ public class DisplayController {
}
}
+ private void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+ Set<Rect> unrestricted) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i)
+ .onKeepClearAreasChanged(displayId, restricted, unrestricted);
+ }
+ }
+ }
+
private static class DisplayRecord {
private int mDisplayId;
private Context mContext;
@@ -296,6 +328,15 @@ public class DisplayController {
DisplayController.this.onFixedRotationFinished(displayId);
});
}
+
+ @Override
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {
+ mMainExecutor.execute(() -> {
+ DisplayController.this.onKeepClearAreasChanged(displayId,
+ new ArraySet<>(restricted), new ArraySet<>(unrestricted));
+ });
+ }
}
/**
@@ -330,5 +371,11 @@ public class DisplayController {
* Called when fixed rotation on a display is finished.
*/
default void onFixedRotationFinished(int displayId) {}
+
+ /**
+ * Called when keep-clear areas on a display have changed.
+ */
+ default void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+ Set<Rect> unrestricted) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index eea6e3cb35db..c4bd73ba1b4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common;
-import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -63,8 +62,6 @@ public class ScreenshotUtils {
if (buffer == null || buffer.getHardwareBuffer() == null) {
return;
}
- final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- buffer.getHardwareBuffer());
mScreenshot = new SurfaceControl.Builder()
.setName("ScreenshotUtils screenshot")
.setFormat(PixelFormat.TRANSLUCENT)
@@ -73,7 +70,7 @@ public class ScreenshotUtils {
.setBLASTLayer()
.build();
- mTransaction.setBuffer(mScreenshot, graphicBuffer);
+ mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
mTransaction.reparent(mScreenshot, mSurfaceControl);
mTransaction.setLayer(mScreenshot, mLayer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 97c89d042be0..d5875c03ccd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
@@ -192,6 +191,19 @@ public class SystemWindows {
return null;
}
+ /**
+ * Gets a token associated with the view that can be used to grant the view focus.
+ */
+ public IBinder getFocusGrantToken(View view) {
+ SurfaceControlViewHost root = mViewRoots.get(view);
+ if (root == null) {
+ Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
+ + "SystemWindow:" + view);
+ return null;
+ }
+ return root.getFocusGrantToken();
+ }
+
private class PerDisplay {
final int mDisplayId;
private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
@@ -331,18 +343,13 @@ public class SystemWindows {
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
- MergedConfiguration newMergedConfiguration, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId) {}
-
- @Override
- public void locationInParentDisplayChanged(Point offset) {}
-
- @Override
- public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {}
+ MergedConfiguration newMergedConfiguration, InsetsState insetsState,
+ boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+ int resizeMode) {}
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {}
+ InsetsSourceControl[] activeControls) {}
@Override
public void showInsets(int types, boolean fromIme) {}
@@ -360,9 +367,6 @@ public class SystemWindows {
public void dispatchGetNewSurface() {}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {}
-
- @Override
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 9e012598554b..aac1d0626d30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -17,13 +17,10 @@ package com.android.wm.shell.common.magnetictarget
import android.annotation.SuppressLint
import android.content.Context
-import android.database.ContentObserver
import android.graphics.PointF
-import android.os.Handler
-import android.os.UserHandle
+import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.os.Vibrator
-import android.provider.Settings
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
@@ -147,6 +144,8 @@ abstract class MagnetizedObject<T : Any>(
private val velocityTracker: VelocityTracker = VelocityTracker.obtain()
private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ private val vibrationAttributes: VibrationAttributes = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_TOUCH)
private var touchDown = PointF()
private var touchSlop = 0
@@ -268,10 +267,6 @@ abstract class MagnetizedObject<T : Any>(
*/
var flungIntoTargetSpringConfig = springConfig
- init {
- initHapticSettingObserver(context)
- }
-
/**
* Adds the provided MagneticTarget to this object. The object will now be attracted to the
* target if it strays within its magnetic field or is flung towards it.
@@ -468,8 +463,8 @@ abstract class MagnetizedObject<T : Any>(
/** Plays the given vibration effect if haptics are enabled. */
@SuppressLint("MissingPermission")
private fun vibrateIfEnabled(effectId: Int) {
- if (hapticsEnabled && systemHapticsEnabled) {
- vibrator.vibrate(VibrationEffect.createPredefined(effectId))
+ if (hapticsEnabled) {
+ vibrator.vibrate(VibrationEffect.createPredefined(effectId), vibrationAttributes)
}
}
@@ -622,44 +617,6 @@ abstract class MagnetizedObject<T : Any>(
}
companion object {
-
- /**
- * Whether the HAPTIC_FEEDBACK_ENABLED setting is true.
- *
- * We put it in the companion object because we need to register a settings observer and
- * [MagnetizedObject] doesn't have an obvious lifecycle so we don't have a good time to
- * remove that observer. Since this settings is shared among all instances we just let all
- * instances read from this value.
- */
- private var systemHapticsEnabled = false
- private var hapticSettingObserverInitialized = false
-
- private fun initHapticSettingObserver(context: Context) {
- if (hapticSettingObserverInitialized) {
- return
- }
-
- val hapticSettingObserver =
- object : ContentObserver(Handler.getMain()) {
- override fun onChange(selfChange: Boolean) {
- systemHapticsEnabled =
- Settings.System.getIntForUser(
- context.contentResolver,
- Settings.System.HAPTIC_FEEDBACK_ENABLED,
- 0,
- UserHandle.USER_CURRENT) != 0
- }
- }
-
- context.contentResolver.registerContentObserver(
- Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED),
- true /* notifyForDescendants */, hapticSettingObserver)
-
- // Trigger the observer once to initialize systemHapticsEnabled.
- hapticSettingObserver.onChange(false /* selfChange */)
- hapticSettingObserverInitialized = true
- }
-
/**
* Magnetizes the given view. Magnetized views are attracted to one or more magnetic
* targets. Magnetic targets attract objects that are dragged near them, and hold them there
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 36e55bae18c3..5dc6bd19853a 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
@@ -21,8 +21,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -42,6 +44,8 @@ import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SurfaceUtils;
@@ -52,6 +56,7 @@ import com.android.wm.shell.common.SurfaceUtils;
public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
+ private static final long FADE_DURATION = 133;
private final IconProvider mIconProvider;
private final SurfaceSession mSurfaceSession;
@@ -63,6 +68,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
+ private boolean mShown;
+ private boolean mIsResizing;
+ private Rect mBounds = new Rect();
+ private ValueAnimator mFadeAnimator;
+
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
super(configuration, null /* rootSurface */, null /* hostInputToken */);
@@ -113,6 +123,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Releases the surfaces for split decor. */
public void release(SurfaceControl.Transaction t) {
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -128,6 +141,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
mHostLeash = null;
mIcon = null;
mResizingIconView = null;
+ mIsResizing = false;
+ mShown = false;
}
/** Showing resizing hint. */
@@ -137,16 +152,19 @@ public class SplitDecorManager extends WindowlessWindowManager {
return;
}
+ if (!mIsResizing) {
+ mIsResizing = true;
+ mBounds.set(newBounds);
+ }
+
if (mBackgroundLeash == null) {
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
- .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
- .show(mBackgroundLeash);
+ .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
if (mIcon == null && resizingTask.topActivityInfo != null) {
- // TODO: add fade-in animation.
mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
mResizingIconView.setImageDrawable(mIcon);
mResizingIconView.setVisibility(View.VISIBLE);
@@ -156,20 +174,99 @@ public class SplitDecorManager extends WindowlessWindowManager {
lp.width = mIcon.getIntrinsicWidth();
lp.height = mIcon.getIntrinsicHeight();
mViewHost.relayout(lp);
- t.show(mIconLeash).setLayer(mIconLeash, SPLIT_DIVIDER_LAYER);
+ t.setLayer(mIconLeash, Integer.MAX_VALUE);
}
-
t.setPosition(mIconLeash,
newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2,
newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2);
+
+ boolean show = newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
+ if (show != mShown) {
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+ startFadeAnimation(show, false /* isResized */);
+ mShown = show;
+ }
}
/** Stops showing resizing hint. */
- public void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+ public void onResized(SurfaceControl.Transaction t) {
if (mResizingIconView == null) {
return;
}
+ mIsResizing = false;
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ if (!mShown) {
+ // If fade-out animation is running, just add release callback to it.
+ SurfaceControl.Transaction finishT = new SurfaceControl.Transaction();
+ mFadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ releaseDecor(finishT);
+ finishT.apply();
+ finishT.close();
+ }
+ });
+ return;
+ }
+
+ // If fade-in animation is running, cancel it and re-run fade-out one.
+ mFadeAnimator.cancel();
+ }
+ if (mShown) {
+ startFadeAnimation(false /* show */, true /* isResized */);
+ } else {
+ // Decor surface is hidden so release it directly.
+ releaseDecor(t);
+ }
+ }
+
+ private void startFadeAnimation(boolean show, boolean isResized) {
+ final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+ mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mFadeAnimator.setDuration(FADE_DURATION);
+ mFadeAnimator.addUpdateListener(valueAnimator-> {
+ final float progress = (float) valueAnimator.getAnimatedValue();
+ if (mBackgroundLeash != null) {
+ animT.setAlpha(mBackgroundLeash, show ? progress : 1 - progress);
+ }
+ if (mIconLeash != null) {
+ animT.setAlpha(mIconLeash, show ? progress : 1 - progress);
+ }
+ animT.apply();
+ });
+ mFadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+ if (show) {
+ animT.show(mBackgroundLeash).show(mIconLeash).apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ if (!show) {
+ if (mBackgroundLeash != null) {
+ animT.hide(mBackgroundLeash);
+ }
+ if (mIconLeash != null) {
+ animT.hide(mIconLeash);
+ }
+ }
+ if (isResized) {
+ releaseDecor(animT);
+ }
+ animT.apply();
+ animT.close();
+ }
+ });
+ mFadeAnimator.start();
+ }
+
+ /** Release or hide decor hint. */
+ private void releaseDecor(SurfaceControl.Transaction t) {
if (mBackgroundLeash != null) {
t.remove(mBackgroundLeash);
mBackgroundLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ba343cb12085..116d3524e711 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
@@ -23,7 +23,6 @@ import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
@@ -93,11 +92,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final InsetsState mInsetsState = new InsetsState();
private Context mContext;
- private DividerSnapAlgorithm mDividerSnapAlgorithm;
+ @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
private WindowContainerToken mWinToken1;
private WindowContainerToken mWinToken2;
private int mDividePosition;
private boolean mInitialized = false;
+ private boolean mFreezeDividerWindow = false;
private int mOrientation;
private int mRotation;
@@ -123,7 +123,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
mRootBounds.set(configuration.windowConfiguration.getBounds());
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
resetDividerPosition();
}
@@ -144,21 +144,42 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return Math.max(dividerInset, radius);
}
- /** Gets bounds of the primary split. */
+ /** Gets bounds of the primary split with screen based coordinate. */
public Rect getBounds1() {
return new Rect(mBounds1);
}
- /** Gets bounds of the secondary split. */
+ /** Gets bounds of the primary split with parent based coordinate. */
+ public Rect getRefBounds1() {
+ Rect outBounds = getBounds1();
+ outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ return outBounds;
+ }
+
+ /** Gets bounds of the secondary split with screen based coordinate. */
public Rect getBounds2() {
return new Rect(mBounds2);
}
- /** Gets bounds of divider window. */
+ /** Gets bounds of the secondary split with parent based coordinate. */
+ public Rect getRefBounds2() {
+ final Rect outBounds = getBounds2();
+ outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ return outBounds;
+ }
+
+ /** Gets bounds of divider window with screen based coordinate. */
public Rect getDividerBounds() {
return new Rect(mDividerBounds);
}
+ /** Gets bounds of divider window with parent based coordinate. */
+ public Rect getRefDividerBounds() {
+ final Rect outBounds = getDividerBounds();
+ outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ return outBounds;
+ }
+
/** Returns leash of the current divider bar. */
@Nullable
public SurfaceControl getDividerLeash() {
@@ -180,38 +201,50 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
public boolean updateConfiguration(Configuration configuration) {
- boolean affectsLayout = false;
+ // Always update configuration after orientation changed to make sure to render divider bar
+ // with proper resources that matching screen orientation.
+ final int orientation = configuration.orientation;
+ if (mOrientation != orientation) {
+ mContext = mContext.createConfigurationContext(configuration);
+ mSplitWindowManager.setConfiguration(configuration);
+ mOrientation = orientation;
+ }
// Update the split bounds when necessary. Besides root bounds changed, split bounds need to
// be updated when the rotation changed to cover the case that users rotated the screen 180
// degrees.
- // Make sure to render the divider bar with proper resources that matching the screen
- // orientation.
final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
- final int orientation = configuration.orientation;
-
- if (mOrientation == orientation
- && rotation == mRotation
- && mRootBounds.equals(rootBounds)) {
+ if (mRotation == rotation && mRootBounds.equals(rootBounds)) {
return false;
}
- mContext = mContext.createConfigurationContext(configuration);
- mSplitWindowManager.setConfiguration(configuration);
- mOrientation = orientation;
mTempRect.set(mRootBounds);
mRootBounds.set(rootBounds);
mRotation = rotation;
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
initDividerPosition(mTempRect);
- if (mInitialized) {
- release();
- init();
+ return true;
+ }
+
+ /** Rotate the layout to specific rotation and calculate new bounds. The stable insets value
+ * should be calculated by display layout. */
+ public void rotateTo(int newRotation, Rect stableInsets) {
+ final int rotationDelta = (newRotation - mRotation + 4) % 4;
+ final boolean changeOrient = (rotationDelta % 2) != 0;
+
+ mRotation = newRotation;
+ Rect tmpRect = new Rect(mRootBounds);
+ if (changeOrient) {
+ tmpRect.set(mRootBounds.top, mRootBounds.left, mRootBounds.bottom, mRootBounds.right);
}
- return true;
+ // We only need new bounds here, other configuration should be update later.
+ mTempRect.set(mRootBounds);
+ mRootBounds.set(tmpRect);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, stableInsets);
+ initDividerPosition(mTempRect);
}
private void initDividerPosition(Rect oldBounds) {
@@ -260,20 +293,37 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
/** Releases the surface holding the current {@link DividerView}. */
- public void release() {
+ public void release(SurfaceControl.Transaction t) {
if (!mInitialized) return;
mInitialized = false;
- mSplitWindowManager.release();
+ mSplitWindowManager.release(t);
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
}
+ public void release() {
+ release(null /* t */);
+ }
+
+ /** Releases and re-inflates {@link DividerView} on the root surface. */
+ public void update(SurfaceControl.Transaction t) {
+ if (!mInitialized) return;
+ mSplitWindowManager.release(t);
+ mImePositionProcessor.reset();
+ mSplitWindowManager.init(this, mInsetsState);
+ }
+
@Override
public void insetsChanged(InsetsState insetsState) {
mInsetsState.set(insetsState);
if (!mInitialized) {
return;
}
+ if (mFreezeDividerWindow) {
+ // DO NOT change its layout before transition actually run because it might cause
+ // flicker.
+ return;
+ }
mSplitWindowManager.onInsetsChanged(insetsState);
}
@@ -285,6 +335,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
+ public void setFreezeDividerWindow(boolean freezeDividerWindow) {
+ mFreezeDividerWindow = freezeDividerWindow;
+ }
+
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
@@ -294,20 +348,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mSplitLayoutHandler.onLayoutSizeChanging(this);
}
- void setDividePosition(int position) {
+ void setDividePosition(int position, boolean applyLayoutChange) {
mDividePosition = position;
updateBounds(mDividePosition);
- mSplitLayoutHandler.onLayoutSizeChanged(this);
+ if (applyLayoutChange) {
+ mSplitLayoutHandler.onLayoutSizeChanged(this);
+ }
}
- /** Sets divide position base on the ratio within root bounds. */
+ /** Updates divide position and split bounds base on the ratio within root bounds. */
public void setDivideRatio(float ratio) {
final int position = isLandscape()
? mRootBounds.left + (int) (mRootBounds.width() * ratio)
: mRootBounds.top + (int) (mRootBounds.height() * ratio);
- DividerSnapAlgorithm.SnapTarget snapTarget =
+ final DividerSnapAlgorithm.SnapTarget snapTarget =
mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
- setDividePosition(snapTarget.position);
+ setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
/** Resets divider position. */
@@ -335,7 +391,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position, null);
+ flingDividePosition(currentPosition, snapTarget.position,
+ () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
}
@@ -353,7 +410,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
- private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
+ private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds,
+ @Nullable Rect stableInsets) {
final boolean isLandscape = isLandscape(rootBounds);
return new DividerSnapAlgorithm(
context.getResources(),
@@ -361,7 +419,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
rootBounds.height(),
mDividerSize,
!isLandscape,
- getDisplayInsets(context),
+ stableInsets != null ? stableInsets : getDisplayInsets(context),
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
@@ -381,7 +439,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- setDividePosition(to);
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
@@ -389,7 +446,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public void onAnimationCancel(Animator animation) {
- setDividePosition(to);
+ setDividePosition(to, true /* applyLayoutChange */);
}
});
animator.start();
@@ -432,14 +489,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
- t.setPosition(dividerLeash, mDividerBounds.left, mDividerBounds.top);
+ mTempRect.set(getRefDividerBounds());
+ t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
// Resets layer of divider bar to make sure it is always on top.
- t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER);
+ t.setLayer(dividerLeash, Integer.MAX_VALUE);
}
- t.setPosition(leash1, mBounds1.left, mBounds1.top)
- .setWindowCrop(leash1, mBounds1.width(), mBounds1.height());
- t.setPosition(leash2, mBounds2.left, mBounds2.top)
- .setWindowCrop(leash2, mBounds2.width(), mBounds2.height());
+ mTempRect.set(getRefBounds1());
+ t.setPosition(leash1, mTempRect.left, mTempRect.top)
+ .setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
+ mTempRect.set(getRefBounds2());
+ t.setPosition(leash2, mTempRect.left, mTempRect.top)
+ .setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
@@ -452,22 +512,28 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
/** Apply recorded task layout to the {@link WindowContainerTransaction}. */
public void applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
- if (mImePositionProcessor.applyTaskLayoutForIme(wct, task1.token, task2.token)) {
- return;
- }
-
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
wct.setBounds(task1.token, mBounds1);
+ wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
wct.setBounds(task2.token, mBounds2);
+ wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
}
}
+ private int getSmallestWidthDp(Rect bounds) {
+ mTempRect.set(bounds);
+ mTempRect.inset(getDisplayInsets(mContext));
+ final int minWidth = Math.min(mTempRect.width(), mTempRect.height());
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ return (int) (minWidth / density);
+ }
+
/**
* Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
* restore shifted configuration bounds if it's no longer shifted.
@@ -687,6 +753,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final int mDisplayId;
+ private boolean mHasImeFocus;
private boolean mImeShown;
private int mYOffsetForIme;
private float mDimValue1;
@@ -709,25 +776,32 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
- if (displayId != mDisplayId) return 0;
+ if (displayId != mDisplayId || !mInitialized) {
+ return 0;
+ }
+
final int imeTargetPosition = getImeTargetPosition();
- if (!mInitialized || imeTargetPosition == SPLIT_POSITION_UNDEFINED) return 0;
+ mHasImeFocus = imeTargetPosition != SPLIT_POSITION_UNDEFINED;
+ if (!mHasImeFocus) {
+ return 0;
+ }
+
mStartImeTop = showing ? hiddenTop : shownTop;
mEndImeTop = showing ? shownTop : hiddenTop;
mImeShown = showing;
// Update target dim values
mLastDim1 = mDimValue1;
- mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && showing
+ mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && mImeShown
? ADJUSTED_NONFOCUS_DIM : 0.0f;
mLastDim2 = mDimValue2;
- mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && showing
+ mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && mImeShown
? ADJUSTED_NONFOCUS_DIM : 0.0f;
// Calculate target bounds offset for IME
mLastYOffset = mYOffsetForIme;
final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- && !isFloating && !isLandscape(mRootBounds) && showing;
+ && !isFloating && !isLandscape(mRootBounds) && mImeShown;
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
@@ -746,15 +820,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
// re-inflated.
- mSplitWindowManager.setInteractive(
- !showing || imeTargetPosition == SPLIT_POSITION_UNDEFINED);
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus);
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@Override
public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
- if (displayId != mDisplayId) return;
+ if (displayId != mDisplayId || !mHasImeFocus) return;
onProgress(getProgress(imeTop));
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@@ -762,7 +835,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
- if (displayId != mDisplayId || cancel) return;
+ if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@@ -774,6 +847,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (!controlling && mImeShown) {
reset();
mSplitWindowManager.setInteractive(true);
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
}
@@ -807,6 +881,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
void reset() {
+ mHasImeFocus = false;
mImeShown = false;
mYOffsetForIme = mLastYOffset = mTargetYOffset = 0;
mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f;
@@ -814,26 +889,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
/**
- * Applies adjusted task layout for showing IME.
- *
- * @return {@code false} if there's no need to adjust, otherwise {@code true}
- */
- boolean applyTaskLayoutForIme(WindowContainerTransaction wct,
- WindowContainerToken token1, WindowContainerToken token2) {
- if (mYOffsetForIme == 0) return false;
-
- mTempRect.set(mBounds1);
- mTempRect.offset(0, mYOffsetForIme);
- wct.setBounds(token1, mTempRect);
-
- mTempRect.set(mBounds2);
- mTempRect.offset(0, mYOffsetForIme);
- wct.setBounds(token2, mTempRect);
-
- return true;
- }
-
- /**
* Adjusts surface layout while showing IME.
*
* @return {@code false} if there's no need to adjust, otherwise {@code true}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 4903f9d46dc7..833d9d50701c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -58,6 +58,9 @@ public final class SplitWindowManager extends WindowlessWindowManager {
private SurfaceControl mLeash;
private DividerView mDividerView;
+ // Used to "pass" a transaction to WWM.remove so that view removal can be synchronized.
+ private SurfaceControl.Transaction mSyncTransaction = null;
+
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
void onLeashReady(SurfaceControl leash);
@@ -130,22 +133,38 @@ public final class SplitWindowManager extends WindowlessWindowManager {
* Releases the surface control of the current {@link DividerView} and tear down the view
* hierarchy.
*/
- void release() {
+ void release(@Nullable SurfaceControl.Transaction t) {
if (mDividerView != null) {
mDividerView = null;
}
if (mViewHost != null){
+ mSyncTransaction = t;
mViewHost.release();
+ mSyncTransaction = null;
mViewHost = null;
}
if (mLeash != null) {
- new SurfaceControl.Transaction().remove(mLeash).apply();
+ if (t == null) {
+ new SurfaceControl.Transaction().remove(mLeash).apply();
+ } else {
+ t.remove(mLeash);
+ }
mLeash = null;
}
}
+ @Override
+ protected void removeSurface(SurfaceControl sc) {
+ // This gets called via SurfaceControlViewHost.release()
+ if (mSyncTransaction != null) {
+ mSyncTransaction.remove(sc);
+ } else {
+ super.removeSurface(sc);
+ }
+ }
+
void setInteractive(boolean interactive) {
if (mDividerView == null) return;
mDividerView.setInteractive(interactive);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
index 99dbfe01964c..b87cf47dd93f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
@@ -24,9 +24,12 @@ import com.android.wm.shell.common.annotations.ExternalThread;
@ExternalThread
public interface CompatUI {
/**
- * Called when the keyguard occluded state changes. Removes all compat UIs if the
- * keyguard is now occluded.
- * @param occluded indicates if the keyguard is now occluded.
+ * Called when the keyguard showing state changes. Removes all compat UIs if the
+ * keyguard is now showing.
+ *
+ * <p>Note that if the keyguard is occluded it will also be considered showing.
+ *
+ * @param showing indicates if the keyguard is now showing.
*/
- void onKeyguardOccludedChanged(boolean occluded);
+ void onKeyguardShowingChanged(boolean showing);
}
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 e0b23873a980..99b32a677abe 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
@@ -17,6 +17,8 @@
package com.android.wm.shell.compatui;
import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
@@ -38,6 +40,9 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -46,19 +51,23 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import dagger.Lazy;
+
/**
- * Controls to show/update restart-activity buttons on Tasks based on whether the foreground
+ * Controller to show/update compat UI components on Tasks based on whether the foreground
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
DisplayImeController.ImePositionProcessor {
- /** Callback for size compat UI interaction. */
+ /** Callback for compat UI interaction. */
public interface CompatUICallback {
/** Called when the size compat restart button appears. */
void onSizeCompatRestartButtonAppeared(int taskId);
/** Called when the size compat restart button is clicked. */
void onSizeCompatRestartButtonClicked(int taskId);
+ /** Called when the camera compat control state is updated. */
+ void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
}
private static final String TAG = "CompatUIController";
@@ -70,8 +79,22 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final SparseArray<PerDisplayOnInsetsChangedListener> mOnInsetsChangedListeners =
new SparseArray<>(0);
- /** The showing UIs by task id. */
- private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0);
+ /**
+ * The active Compat Control UI layouts by task id.
+ *
+ * <p>An active layout is a layout that is eligible to be shown for the associated task but
+ * isn't necessarily shown at a given time.
+ */
+ private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);
+
+ /**
+ * The active Letterbox Education layout if there is one (there can be at most one active).
+ *
+ * <p>An active layout is a layout that is eligible to be shown for the associated task but
+ * isn't necessarily shown at a given time.
+ */
+ @Nullable
+ private LetterboxEduWindowManager mActiveLetterboxEduLayout;
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -82,30 +105,35 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
+ private final Lazy<Transitions> mTransitionsLazy;
private final CompatUIImpl mImpl = new CompatUIImpl();
private CompatUICallback mCallback;
- /** Only show once automatically in the process life. */
- private boolean mHasShownHint;
- /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
- * be shown. */
- private boolean mKeyguardOccluded;
+ // Only show each hint once automatically in the process life.
+ private final CompatUIHintsState mCompatUIHintsState;
+
+ // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
+ // be shown.
+ private boolean mKeyguardShowing;
public CompatUIController(Context context,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
SyncTransactionQueue syncQueue,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Lazy<Transitions> transitionsLazy) {
mContext = context;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mImeController = imeController;
mSyncQueue = syncQueue;
mMainExecutor = mainExecutor;
+ mTransitionsLazy = transitionsLazy;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
+ mCompatUIHintsState = new CompatUIHintsState();
}
/** Returns implementation of {@link CompatUI}. */
@@ -122,24 +150,19 @@ public class CompatUIController implements OnDisplaysChangedListener,
* Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
*
- * @param displayId display the task and activity are in.
- * @param taskId task the activity is in.
- * @param taskConfig task config to place the compat UI with.
+ * @param taskInfo {@link TaskInfo} task the activity is in.
* @param taskListener listener to handle the Task Surface placement.
*/
- public void onCompatInfoChanged(int displayId, int taskId,
- @Nullable Configuration taskConfig,
+ public void onCompatInfoChanged(TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
- if (taskConfig == null || taskListener == null) {
+ if (taskInfo.configuration == null || taskListener == null) {
// Null token means the current foreground activity is not in compatibility mode.
- removeLayout(taskId);
- } else if (mActiveLayouts.contains(taskId)) {
- // UI already exists, update the UI layout.
- updateLayout(taskId, taskConfig, taskListener);
- } else {
- // Create a new compat UI.
- createLayout(displayId, taskId, taskConfig, taskListener);
+ removeLayouts(taskInfo.taskId);
+ return;
}
+
+ createOrUpdateCompatLayout(taskInfo, taskListener);
+ createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
}
@Override
@@ -156,7 +179,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
final List<Integer> toRemoveTaskIds = new ArrayList<>();
forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
- removeLayout(toRemoveTaskIds.get(i));
+ removeLayouts(toRemoveTaskIds.get(i));
}
}
@@ -201,59 +224,108 @@ public class CompatUIController implements OnDisplaysChangedListener,
}
@VisibleForTesting
- void onKeyguardOccludedChanged(boolean occluded) {
- mKeyguardOccluded = occluded;
- // Hide the compat UIs when keyguard is occluded.
+ void onKeyguardShowingChanged(boolean showing) {
+ mKeyguardShowing = showing;
+ // Hide the compat UIs when keyguard is showing.
forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
}
private boolean showOnDisplay(int displayId) {
- return !mKeyguardOccluded && !isImeShowingOnDisplay(displayId);
+ return !mKeyguardShowing && !isImeShowingOnDisplay(displayId);
}
private boolean isImeShowingOnDisplay(int displayId) {
return mDisplaysWithIme.contains(displayId);
}
- private void createLayout(int displayId, int taskId, Configuration taskConfig,
+ private void createOrUpdateCompatLayout(TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
- final Context context = getOrCreateDisplayContext(displayId);
- if (context == null) {
- Log.e(TAG, "Cannot get context for display " + displayId);
+ CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
+ if (layout != null) {
+ // UI already exists, update the UI layout.
+ if (!layout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(layout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, remove from active layouts.
+ mActiveCompatLayouts.remove(taskInfo.taskId);
+ }
return;
}
- final CompatUIWindowManager compatUIWindowManager =
- createLayout(context, displayId, taskId, taskConfig, taskListener);
- mActiveLayouts.put(taskId, compatUIWindowManager);
- compatUIWindowManager.createLayout(showOnDisplay(displayId));
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ layout = createCompatUiWindowManager(context, taskInfo, taskListener);
+ if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mActiveCompatLayouts.put(taskInfo.taskId, layout);
+ }
}
@VisibleForTesting
- CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
- Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
- final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
- taskConfig, mSyncQueue, mCallback, taskId, taskListener,
- mDisplayController.getDisplayLayout(displayId), mHasShownHint);
- // Only show hint for the first time.
- mHasShownHint = true;
- return compatUIWindowManager;
+ CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new CompatUIWindowManager(context,
+ taskInfo, mSyncQueue, mCallback, taskListener,
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
}
- private void updateLayout(int taskId, Configuration taskConfig,
+ private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
- final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
- if (layout == null) {
+ if (mActiveLetterboxEduLayout != null
+ && mActiveLetterboxEduLayout.getTaskId() == taskInfo.taskId) {
+ // UI already exists, update the UI layout.
+ if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, clear active layout.
+ mActiveLetterboxEduLayout = null;
+ }
+ return;
+ }
+
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
return;
}
- layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
+ LetterboxEduWindowManager newLayout = createLetterboxEduWindowManager(context, taskInfo,
+ taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, make it the active layout.
+ if (mActiveLetterboxEduLayout != null) {
+ // Release the previous layout since at most one can be active.
+ // Since letterbox education is only shown once to the user, releasing the previous
+ // layout is only a precaution.
+ mActiveLetterboxEduLayout.release();
+ }
+ mActiveLetterboxEduLayout = newLayout;
+ }
}
- private void removeLayout(int taskId) {
- final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+ @VisibleForTesting
+ LetterboxEduWindowManager createLetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new LetterboxEduWindowManager(context, taskInfo,
+ mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mTransitionsLazy.get(),
+ this::onLetterboxEduDismissed);
+ }
+
+ private void onLetterboxEduDismissed() {
+ mActiveLetterboxEduLayout = null;
+ }
+
+ private void removeLayouts(int taskId) {
+ final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
if (layout != null) {
layout.release();
- mActiveLayouts.remove(taskId);
+ mActiveCompatLayouts.remove(taskId);
+ }
+
+ if (mActiveLetterboxEduLayout != null && mActiveLetterboxEduLayout.getTaskId() == taskId) {
+ mActiveLetterboxEduLayout.release();
+ mActiveLetterboxEduLayout = null;
}
}
@@ -271,28 +343,34 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (display != null) {
context = mContext.createDisplayContext(display);
mDisplayContextCache.put(displayId, new WeakReference<>(context));
+ } else {
+ Log.e(TAG, "Cannot get context for display " + displayId);
}
}
return context;
}
- private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) {
+ private void forAllLayoutsOnDisplay(int displayId,
+ Consumer<CompatUIWindowManagerAbstract> callback) {
forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
}
- private void forAllLayouts(Consumer<CompatUIWindowManager> callback) {
+ private void forAllLayouts(Consumer<CompatUIWindowManagerAbstract> callback) {
forAllLayouts(layout -> true, callback);
}
- private void forAllLayouts(Predicate<CompatUIWindowManager> condition,
- Consumer<CompatUIWindowManager> callback) {
- for (int i = 0; i < mActiveLayouts.size(); i++) {
- final int taskId = mActiveLayouts.keyAt(i);
- final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+ private void forAllLayouts(Predicate<CompatUIWindowManagerAbstract> condition,
+ Consumer<CompatUIWindowManagerAbstract> callback) {
+ for (int i = 0; i < mActiveCompatLayouts.size(); i++) {
+ final int taskId = mActiveCompatLayouts.keyAt(i);
+ final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
if (layout != null && condition.test(layout)) {
callback.accept(layout);
}
}
+ if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
+ callback.accept(mActiveLetterboxEduLayout);
+ }
}
/**
@@ -301,9 +379,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
@ExternalThread
private class CompatUIImpl implements CompatUI {
@Override
- public void onKeyguardOccludedChanged(boolean occluded) {
+ public void onKeyguardShowingChanged(boolean showing) {
mMainExecutor.execute(() -> {
- CompatUIController.this.onKeyguardOccludedChanged(occluded);
+ CompatUIController.this.onKeyguardShowingChanged(showing);
});
}
}
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 ea4f20968438..d44b4d8f63b6 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
@@ -16,6 +16,9 @@
package com.android.wm.shell.compatui;
+import android.annotation.IdRes;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
@@ -28,7 +31,7 @@ import com.android.wm.shell.R;
/**
* Container for compat UI controls.
*/
-public class CompatUILayout extends LinearLayout {
+class CompatUILayout extends LinearLayout {
private CompatUIWindowManager mWindowManager;
@@ -53,21 +56,59 @@ public class CompatUILayout extends LinearLayout {
mWindowManager = windowManager;
}
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- // Need to relayout after changes like hiding / showing a hint since they affect size.
- // Doing this directly in setSizeCompatHintVisibility can result in flaky animation.
- mWindowManager.relayout();
+ void updateCameraTreatmentButton(@CameraCompatControlState int newState) {
+ int buttonBkgId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+ ? R.drawable.camera_compat_treatment_suggested_ripple
+ : R.drawable.camera_compat_treatment_applied_ripple;
+ int hintStringId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+ ? R.string.camera_compat_treatment_suggested_button_description
+ : R.string.camera_compat_treatment_applied_button_description;
+ final ImageButton button = findViewById(R.id.camera_compat_treatment_button);
+ button.setImageResource(buttonBkgId);
+ button.setContentDescription(getResources().getString(hintStringId));
+ final LinearLayout hint = findViewById(R.id.camera_compat_hint);
+ ((TextView) hint.findViewById(R.id.compat_mode_hint_text)).setText(hintStringId);
}
void setSizeCompatHintVisibility(boolean show) {
- final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
+ setViewVisibility(R.id.size_compat_hint, show);
+ }
+
+ void setCameraCompatHintVisibility(boolean show) {
+ setViewVisibility(R.id.camera_compat_hint, show);
+ }
+
+ void setRestartButtonVisibility(boolean show) {
+ setViewVisibility(R.id.size_compat_restart_button, show);
+ // Hint should never be visible without button.
+ if (!show) {
+ setSizeCompatHintVisibility(/* show= */ false);
+ }
+ }
+
+ void setCameraControlVisibility(boolean show) {
+ setViewVisibility(R.id.camera_compat_control, show);
+ // Hint should never be visible without button.
+ if (!show) {
+ setCameraCompatHintVisibility(/* show= */ false);
+ }
+ }
+
+ private void setViewVisibility(@IdRes int resId, boolean show) {
+ final View view = findViewById(resId);
int visibility = show ? View.VISIBLE : View.GONE;
- if (sizeCompatHint.getVisibility() == visibility) {
+ if (view.getVisibility() == visibility) {
return;
}
- sizeCompatHint.setVisibility(visibility);
+ view.setVisibility(visibility);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Need to relayout after changes like hiding / showing a hint since they affect size.
+ // Doing this directly in setSizeCompatHintVisibility can result in flaky animation.
+ mWindowManager.relayout();
}
@Override
@@ -85,5 +126,26 @@ public class CompatUILayout extends LinearLayout {
((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
.setText(R.string.restart_button_description);
sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false));
+
+ final ImageButton cameraTreatmentButton =
+ findViewById(R.id.camera_compat_treatment_button);
+ cameraTreatmentButton.setOnClickListener(
+ view -> mWindowManager.onCameraTreatmentButtonClicked());
+ cameraTreatmentButton.setOnLongClickListener(view -> {
+ mWindowManager.onCameraButtonLongClicked();
+ return true;
+ });
+
+ final ImageButton cameraDismissButton = findViewById(R.id.camera_compat_dismiss_button);
+ cameraDismissButton.setOnClickListener(
+ view -> mWindowManager.onCameraDismissButtonClicked());
+ cameraDismissButton.setOnLongClickListener(view -> {
+ mWindowManager.onCameraButtonLongClicked();
+ return true;
+ });
+
+ final LinearLayout cameraCompatHint = findViewById(R.id.camera_compat_hint);
+ cameraCompatHint.setOnClickListener(
+ view -> setCameraCompatHintVisibility(/* show= */ false));
}
}
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 997ad04e3b57..bce3ec4128e8 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
@@ -16,178 +16,124 @@
package com.android.wm.shell.compatui;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.os.Binder;
import android.util.Log;
-import android.view.IWindow;
import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.SurfaceSession;
import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
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.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
/**
- * Holds view hierarchy of a root surface and helps to inflate and manage layout for compat
- * controls.
+ * Window manager for the Size Compat restart button and Camera Compat control.
*/
-class CompatUIWindowManager extends WindowlessWindowManager {
+class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
- private static final String TAG = "CompatUIWindowManager";
+ /**
+ * The Compat UI should be below the Letterbox Education.
+ */
+ private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
- private final SyncTransactionQueue mSyncQueue;
- private final CompatUIController.CompatUICallback mCallback;
- private final int mDisplayId;
- private final int mTaskId;
- private final Rect mStableBounds;
+ private final CompatUICallback mCallback;
- private Context mContext;
- private Configuration mTaskConfig;
- private ShellTaskOrganizer.TaskListener mTaskListener;
- private DisplayLayout mDisplayLayout;
+ // Remember the last reported states in case visibility changes due to keyguard or IME updates.
+ @VisibleForTesting
+ boolean mHasSizeCompat;
@VisibleForTesting
- boolean mShouldShowHint;
+ @CameraCompatControlState
+ int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
- @Nullable
@VisibleForTesting
- CompatUILayout mCompatUILayout;
+ CompatUIHintsState mCompatUIHintsState;
@Nullable
- private SurfaceControlViewHost mViewHost;
- @Nullable
- private SurfaceControl mLeash;
-
- CompatUIWindowManager(Context context, Configuration taskConfig,
- SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
- int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- boolean hasShownHint) {
- super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
- mContext = context;
- mSyncQueue = syncQueue;
+ @VisibleForTesting
+ CompatUILayout mLayout;
+
+ CompatUIWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, CompatUICallback callback,
+ ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+ CompatUIHintsState compatUIHintsState) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mTaskConfig = taskConfig;
- mDisplayId = mContext.getDisplayId();
- mTaskId = taskId;
- mTaskListener = taskListener;
- mDisplayLayout = displayLayout;
- mShouldShowHint = !hasShownHint;
- mStableBounds = new Rect();
- mDisplayLayout.getStableBounds(mStableBounds);
+ mHasSizeCompat = taskInfo.topActivityInSizeCompat;
+ mCameraCompatControlState = taskInfo.cameraCompatControlState;
+ mCompatUIHintsState = compatUIHintsState;
}
@Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
- mContext = mContext.createConfigurationContext(configuration);
+ protected int getZOrder() {
+ return Z_ORDER;
}
@Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
- // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
- final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
- .setContainerLayer()
- .setName("CompatUILeash")
- .setHidden(false)
- .setCallsite("CompatUIWindowManager#attachToParentSurface");
- attachToParentSurface(builder);
- mLeash = builder.build();
- b.setParent(mLeash);
+ protected @Nullable View getLayout() {
+ return mLayout;
}
- /** Creates the layout for compat controls. */
- void createLayout(boolean show) {
- if (!show || mCompatUILayout != null) {
- // Wait until compat controls should be visible.
- return;
- }
-
- initCompatUi();
- updateSurfacePosition();
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
- mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mHasSizeCompat || shouldShowCameraControl();
}
- /** Called when compat info changed. */
- void updateCompatInfo(Configuration taskConfig,
- ShellTaskOrganizer.TaskListener taskListener, boolean show) {
- final Configuration prevTaskConfig = mTaskConfig;
- final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
- mTaskConfig = taskConfig;
- mTaskListener = taskListener;
-
- // Update configuration.
- mContext = mContext.createConfigurationContext(taskConfig);
- setConfiguration(taskConfig);
-
- if (mCompatUILayout == null || prevTaskListener != taskListener) {
- // TaskListener changed, recreate the layout for new surface parent.
- release();
- createLayout(show);
- return;
- }
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
- if (!taskConfig.windowConfiguration.getBounds()
- .equals(prevTaskConfig.windowConfiguration.getBounds())) {
- // Reposition the UI surfaces.
- updateSurfacePosition();
- }
+ updateVisibilityOfViews();
- if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
- // Update layout for RTL.
- mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection());
- updateSurfacePosition();
+ if (mHasSizeCompat) {
+ mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
}
+
+ return mLayout;
}
- /** Called when the visibility of the UI should change. */
- void updateVisibility(boolean show) {
- if (mCompatUILayout == null) {
- // Layout may not have been created because it was hidden previously.
- createLayout(show);
- return;
- }
+ @VisibleForTesting
+ CompatUILayout inflateLayout() {
+ return (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout,
+ null);
+ }
- // Hide compat UIs when IME is showing.
- final int newVisibility = show ? View.VISIBLE : View.GONE;
- if (mCompatUILayout.getVisibility() != newVisibility) {
- mCompatUILayout.setVisibility(newVisibility);
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ final boolean prevHasSizeCompat = mHasSizeCompat;
+ final int prevCameraCompatControlState = mCameraCompatControlState;
+ mHasSizeCompat = taskInfo.topActivityInSizeCompat;
+ mCameraCompatControlState = taskInfo.cameraCompatControlState;
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
}
- }
- /** Called when display layout changed. */
- void updateDisplayLayout(DisplayLayout displayLayout) {
- final Rect prevStableBounds = mStableBounds;
- final Rect curStableBounds = new Rect();
- displayLayout.getStableBounds(curStableBounds);
- mDisplayLayout = displayLayout;
- if (!prevStableBounds.equals(curStableBounds)) {
- // Stable bounds changed, update UI surface positions.
- updateSurfacePosition();
- mStableBounds.set(curStableBounds);
+ if (prevHasSizeCompat != mHasSizeCompat
+ || prevCameraCompatControlState != mCameraCompatControlState) {
+ updateVisibilityOfViews();
}
- }
- /** Called when it is ready to be placed compat UI surface. */
- void attachToParentSurface(SurfaceControl.Builder b) {
- mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+ return true;
}
/** Called when the restart button is clicked. */
@@ -195,127 +141,105 @@ class CompatUIWindowManager extends WindowlessWindowManager {
mCallback.onSizeCompatRestartButtonClicked(mTaskId);
}
- /** Called when the restart button is long clicked. */
- void onRestartButtonLongClicked() {
- if (mCompatUILayout == null) {
+ /** Called when the camera treatment button is clicked. */
+ void onCameraTreatmentButtonClicked() {
+ if (!shouldShowCameraControl()) {
+ Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state.");
return;
}
- mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
- }
-
- int getDisplayId() {
- return mDisplayId;
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- /** Releases the surface control and tears down the view hierarchy. */
- void release() {
- mCompatUILayout = null;
-
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
+ // When a camera control is shown, only two states are allowed: "treament applied" and
+ // "treatment suggested". Clicks on the conrol's treatment button toggle between these
+ // two states.
+ mCameraCompatControlState =
+ mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+ ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
+ : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+ mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
+ }
+
+ /** Called when the camera dismiss button is clicked. */
+ void onCameraDismissButtonClicked() {
+ if (!shouldShowCameraControl()) {
+ Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state.");
+ return;
}
+ mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
+ mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+ mLayout.setCameraControlVisibility(/* show= */ false);
+ }
- if (mLeash != null) {
- final SurfaceControl leash = mLeash;
- mSyncQueue.runInSync(t -> t.remove(leash));
- mLeash = null;
+ /** Called when the restart button is long clicked. */
+ void onRestartButtonLongClicked() {
+ if (mLayout == null) {
+ return;
}
+ mLayout.setSizeCompatHintVisibility(/* show= */ true);
}
- void relayout() {
- mViewHost.relayout(getWindowLayoutParams());
- updateSurfacePosition();
+ /** Called when either dismiss or treatment camera buttons is long clicked. */
+ void onCameraButtonLongClicked() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setCameraCompatHintVisibility(/* show= */ true);
}
+ @Override
@VisibleForTesting
- void updateSurfacePosition() {
- if (mCompatUILayout == null || mLeash == null) {
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
return;
}
-
- // Use stable bounds to prevent controls from overlapping with system bars.
- final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
- final Rect stableBounds = new Rect();
- mDisplayLayout.getStableBounds(stableBounds);
- stableBounds.intersect(taskBounds);
-
// Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.left - taskBounds.left
- : stableBounds.right - taskBounds.left - mCompatUILayout.getMeasuredWidth();
- final int positionY = stableBounds.bottom - taskBounds.top
- - mCompatUILayout.getMeasuredHeight();
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
+ final int positionY = taskStableBounds.bottom - taskBounds.top
+ - mLayout.getMeasuredHeight();
updateSurfacePosition(positionX, positionY);
}
- private int getLayoutDirection() {
- return mContext.getResources().getConfiguration().getLayoutDirection();
- }
-
- private void updateSurfacePosition(int positionX, int positionY) {
- mSyncQueue.runInSync(t -> {
- if (mLeash == null || !mLeash.isValid()) {
- Log.w(TAG, "The leash has been released.");
- return;
- }
- t.setPosition(mLeash, positionX, positionY);
- // The compat UI should be the topmost child of the Task in case there can be more
- // than one children.
- t.setLayer(mLeash, Integer.MAX_VALUE);
- });
- }
-
- /** Inflates {@link CompatUILayout} on to the root surface. */
- private void initCompatUi() {
- if (mViewHost != null) {
- throw new IllegalStateException(
- "A UI has already been created with this window manager.");
+ private void updateVisibilityOfViews() {
+ if (mLayout == null) {
+ return;
}
-
- // Construction extracted into the separate methods to allow injection for tests.
- mViewHost = createSurfaceViewHost();
- mCompatUILayout = inflateCompatUILayout();
- mCompatUILayout.inject(this);
-
- mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint);
-
- mViewHost.setView(mCompatUILayout, getWindowLayoutParams());
-
+ // Size Compat mode restart button.
+ mLayout.setRestartButtonVisibility(mHasSizeCompat);
// Only show by default for the first time.
- mShouldShowHint = false;
- }
+ if (mHasSizeCompat && !mCompatUIHintsState.mHasShownSizeCompatHint) {
+ mLayout.setSizeCompatHintVisibility(/* show= */ true);
+ mCompatUIHintsState.mHasShownSizeCompatHint = true;
+ }
- @VisibleForTesting
- CompatUILayout inflateCompatUILayout() {
- return (CompatUILayout) LayoutInflater.from(mContext)
- .inflate(R.layout.compat_ui_layout, null);
+ // Camera control for stretched issues.
+ mLayout.setCameraControlVisibility(shouldShowCameraControl());
+ // Only show by default for the first time.
+ if (shouldShowCameraControl() && !mCompatUIHintsState.mHasShownCameraCompatHint) {
+ mLayout.setCameraCompatHintVisibility(/* show= */ true);
+ mCompatUIHintsState.mHasShownCameraCompatHint = true;
+ }
+ if (shouldShowCameraControl()) {
+ mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
+ }
}
- @VisibleForTesting
- SurfaceControlViewHost createSurfaceViewHost() {
- return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ private boolean shouldShowCameraControl() {
+ return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
+ && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
}
- /** Gets the layout params. */
- private WindowManager.LayoutParams getWindowLayoutParams() {
- // Measure how big the hint is since its size depends on the text size.
- mCompatUILayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
- // Cannot be wrap_content as this determines the actual window size
- mCompatUILayout.getMeasuredWidth(), mCompatUILayout.getMeasuredHeight(),
- TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
- PixelFormat.TRANSLUCENT);
- winParams.token = new Binder();
- winParams.setTitle(CompatUILayout.class.getSimpleName() + mTaskId);
- winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- return winParams;
+ /**
+ * A class holding the state of the compat UI hints, which is shared between all compat UI
+ * window managers.
+ */
+ static class CompatUIHintsState {
+ @VisibleForTesting
+ boolean mHasShownSizeCompatHint;
+ @VisibleForTesting
+ boolean mHasShownCameraCompatHint;
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
new file mode 100644
index 000000000000..face24340a4e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -0,0 +1,393 @@
+/*
+ * 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.compatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
+
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * A superclass for all Compat UI {@link WindowlessWindowManager}s that holds shared logic and
+ * exposes general API for {@link CompatUIController}.
+ *
+ * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout.
+ */
+public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
+
+ protected final int mTaskId;
+ protected Context mContext;
+
+ private final SyncTransactionQueue mSyncQueue;
+ private final int mDisplayId;
+ private Configuration mTaskConfig;
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ private DisplayLayout mDisplayLayout;
+ private final Rect mStableBounds;
+
+ /**
+ * Utility class for adding and releasing a View hierarchy for this {@link
+ * WindowlessWindowManager} to {@code mLeash}.
+ */
+ @Nullable
+ protected SurfaceControlViewHost mViewHost;
+
+ /**
+ * A surface leash to position the layout relative to the task, since we can't set position for
+ * the {@code mViewHost} directly.
+ */
+ @Nullable
+ protected SurfaceControl mLeash;
+
+ protected CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout) {
+ super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context;
+ mSyncQueue = syncQueue;
+ mTaskConfig = taskInfo.configuration;
+ mDisplayId = mContext.getDisplayId();
+ mTaskId = taskInfo.taskId;
+ mTaskListener = taskListener;
+ mDisplayLayout = displayLayout;
+ mStableBounds = new Rect();
+ mDisplayLayout.getStableBounds(mStableBounds);
+ }
+
+ /**
+ * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once
+ * {@link #attachToParentSurface} is called.
+ *
+ * <p>See {@link SurfaceControl.Transaction#setLayer}.
+ */
+ protected abstract int getZOrder();
+
+ /** Returns the layout of this window manager. */
+ protected abstract @Nullable View getLayout();
+
+ /**
+ * Inflates and inits the layout of this window manager on to the root surface if both {@code
+ * canShow} and {@link #eligibleToShowLayout} are true.
+ *
+ * <p>Doesn't do anything if layout is not eligible to be shown.
+ *
+ * @param canShow whether the layout is allowed to be shown by the parent controller.
+ * @return whether the layout is eligible to be shown.
+ */
+ @VisibleForTesting(visibility = PROTECTED)
+ public boolean createLayout(boolean canShow) {
+ if (!eligibleToShowLayout()) {
+ return false;
+ }
+ if (!canShow || getLayout() != null) {
+ // Wait until layout should be visible, or layout was already created.
+ return true;
+ }
+
+ if (mViewHost != null) {
+ throw new IllegalStateException(
+ "A UI has already been created with this window manager.");
+ }
+
+ // Construction extracted into separate methods to allow injection for tests.
+ mViewHost = createSurfaceViewHost();
+ mViewHost.setView(createLayout(), getWindowLayoutParams());
+
+ updateSurfacePosition();
+
+ return true;
+ }
+
+ /** Inflates and inits the layout of this window manager. */
+ protected abstract View createLayout();
+
+ protected abstract void removeLayout();
+
+ /**
+ * Whether the layout is eligible to be shown according to the internal state of the subclass.
+ */
+ protected abstract boolean eligibleToShowLayout();
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ String className = getClass().getSimpleName();
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName(className + "Leash")
+ .setHidden(false)
+ .setCallsite(className + "#attachToParentSurface");
+ attachToParentSurface(builder);
+ mLeash = builder.build();
+ b.setParent(mLeash);
+
+ initSurface(mLeash);
+ }
+
+ /** Inits the z-order of the surface. */
+ private void initSurface(SurfaceControl leash) {
+ final int z = getZOrder();
+ mSyncQueue.runInSync(t -> {
+ if (leash == null || !leash.isValid()) {
+ Log.w(getTag(), "The leash has been released.");
+ return;
+ }
+ t.setLayer(leash, z);
+ });
+ }
+
+ /**
+ * Called when compat info changed.
+ *
+ * <p>The window manager is released if the layout is no longer eligible to be shown.
+ *
+ * @param canShow whether the layout is allowed to be shown by the parent controller.
+ * @return whether the layout is eligible to be shown.
+ */
+ @VisibleForTesting(visibility = PROTECTED)
+ public boolean updateCompatInfo(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+ final Configuration prevTaskConfig = mTaskConfig;
+ final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+ mTaskConfig = taskInfo.configuration;
+ mTaskListener = taskListener;
+
+ // Update configuration.
+ setConfiguration(mTaskConfig);
+
+ if (!eligibleToShowLayout()) {
+ release();
+ return false;
+ }
+
+ View layout = getLayout();
+ if (layout == null || prevTaskListener != taskListener) {
+ // Layout wasn't created yet or TaskListener changed, recreate the layout for new
+ // surface parent.
+ release();
+ return createLayout(canShow);
+ }
+
+ boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals(
+ prevTaskConfig.windowConfiguration.getBounds());
+ boolean layoutDirectionUpdated =
+ mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection();
+ if (boundsUpdated || layoutDirectionUpdated) {
+ onParentBoundsChanged();
+ }
+
+ if (layout != null && layoutDirectionUpdated) {
+ // Update layout for RTL.
+ layout.setLayoutDirection(mTaskConfig.getLayoutDirection());
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates the visibility of the layout.
+ *
+ * @param canShow whether the layout is allowed to be shown by the parent controller.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateVisibility(boolean canShow) {
+ View layout = getLayout();
+ if (layout == null) {
+ // Layout may not have been created because it was hidden previously.
+ createLayout(canShow);
+ return;
+ }
+
+ final int newVisibility = canShow && eligibleToShowLayout() ? View.VISIBLE : View.GONE;
+ if (layout.getVisibility() != newVisibility) {
+ layout.setVisibility(newVisibility);
+ }
+ }
+
+ /** Called when display layout changed. */
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateDisplayLayout(DisplayLayout displayLayout) {
+ final Rect prevStableBounds = mStableBounds;
+ final Rect curStableBounds = new Rect();
+ displayLayout.getStableBounds(curStableBounds);
+ mDisplayLayout = displayLayout;
+ if (!prevStableBounds.equals(curStableBounds)) {
+ // mStableBounds should be updated before we call onParentBoundsChanged.
+ mStableBounds.set(curStableBounds);
+ onParentBoundsChanged();
+ }
+ }
+
+ /** Called when the surface is ready to be placed under the task surface. */
+ @VisibleForTesting(visibility = PRIVATE)
+ void attachToParentSurface(SurfaceControl.Builder b) {
+ mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+ }
+
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /** Releases the surface control and tears down the view hierarchy. */
+ public void release() {
+ // Hiding before releasing to avoid flickering when transitioning to the Home screen.
+ View layout = getLayout();
+ if (layout != null) {
+ layout.setVisibility(View.GONE);
+ }
+ removeLayout();
+
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ final SurfaceControl leash = mLeash;
+ mSyncQueue.runInSync(t -> t.remove(leash));
+ mLeash = null;
+ }
+ }
+
+ /** Re-layouts the view host and updates the surface position. */
+ void relayout() {
+ relayout(getWindowLayoutParams());
+ }
+
+ protected void relayout(WindowManager.LayoutParams windowLayoutParams) {
+ if (mViewHost == null) {
+ return;
+ }
+ mViewHost.relayout(windowLayoutParams);
+ updateSurfacePosition();
+ }
+
+ /**
+ * Called following a change in the task bounds, display layout stable bounds, or the layout
+ * direction.
+ */
+ protected void onParentBoundsChanged() {
+ updateSurfacePosition();
+ }
+
+ /**
+ * Updates the position of the surface with respect to the parent bounds.
+ */
+ protected abstract void updateSurfacePosition();
+
+ /**
+ * Updates the position of the surface with respect to the given {@code positionX} and {@code
+ * positionY}.
+ */
+ protected void updateSurfacePosition(int positionX, int positionY) {
+ if (mLeash == null) {
+ return;
+ }
+ mSyncQueue.runInSync(t -> {
+ if (mLeash == null || !mLeash.isValid()) {
+ Log.w(getTag(), "The leash has been released.");
+ return;
+ }
+ t.setPosition(mLeash, positionX, positionY);
+ });
+ }
+
+ protected int getLayoutDirection() {
+ return mContext.getResources().getConfiguration().getLayoutDirection();
+ }
+
+ protected Rect getTaskBounds() {
+ return mTaskConfig.windowConfiguration.getBounds();
+ }
+
+ /** Returns the intersection between the task bounds and the display layout stable bounds. */
+ protected Rect getTaskStableBounds() {
+ final Rect result = new Rect(mStableBounds);
+ result.intersect(getTaskBounds());
+ return result;
+ }
+
+ /** Creates a {@link SurfaceControlViewHost} for this window manager. */
+ @VisibleForTesting(visibility = PRIVATE)
+ public SurfaceControlViewHost createSurfaceViewHost() {
+ return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ }
+
+ /** 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.
+ layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight());
+ }
+
+ /** Gets the layout params given the width and height of the layout. */
+ protected WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
+ final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+ // Cannot be wrap_content as this determines the actual window size
+ width, height,
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT);
+ winParams.token = new Binder();
+ winParams.setTitle(getClass().getSimpleName() + mTaskId);
+ winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ return winParams;
+ }
+
+ protected final String getTag() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
new file mode 100644
index 000000000000..3061eab17d24
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Log;
+import android.util.Property;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.TransitionAnimation;
+
+/**
+ * Controls the enter/exit animations of the letterbox education.
+ */
+class LetterboxEduAnimationController {
+ private static final String TAG = "LetterboxEduAnimation";
+
+ // If shell transitions are enabled, startEnterAnimation will be called after all transitions
+ // have finished, and therefore the start delay should be shorter.
+ private static final int ENTER_ANIM_START_DELAY_MILLIS = ENABLE_SHELL_TRANSITIONS ? 300 : 500;
+
+ private final TransitionAnimation mTransitionAnimation;
+ private final String mPackageName;
+ @AnyRes
+ private final int mAnimStyleResId;
+
+ @Nullable
+ private Animation mDialogAnimation;
+ @Nullable
+ private Animator mBackgroundDimAnimator;
+
+ LetterboxEduAnimationController(Context context) {
+ mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG);
+ mAnimStyleResId = (new ContextThemeWrapper(context,
+ android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
+ com.android.internal.R.styleable.Window).getResourceId(
+ com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+ mPackageName = context.getPackageName();
+ }
+
+ /**
+ * Starts both background dim fade-in animation and the dialog enter animation.
+ */
+ void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+ // Cancel any previous animation if it's still running.
+ cancelAnimation();
+
+ final View dialogContainer = layout.getDialogContainer();
+ mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
+ if (mDialogAnimation == null) {
+ endCallback.run();
+ return;
+ }
+ mDialogAnimation.setAnimationListener(getAnimationListener(
+ /* startCallback= */ () -> dialogContainer.setAlpha(1),
+ /* endCallback= */ () -> {
+ mDialogAnimation = null;
+ endCallback.run();
+ }));
+
+ mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(),
+ /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
+ mDialogAnimation.getDuration());
+ mBackgroundDimAnimator.addListener(getDimAnimatorListener());
+
+ mDialogAnimation.setStartOffset(ENTER_ANIM_START_DELAY_MILLIS);
+ mBackgroundDimAnimator.setStartDelay(ENTER_ANIM_START_DELAY_MILLIS);
+
+ dialogContainer.startAnimation(mDialogAnimation);
+ mBackgroundDimAnimator.start();
+ }
+
+ /**
+ * Starts both the background dim fade-out animation and the dialog exit animation.
+ */
+ void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+ // Cancel any previous animation if it's still running.
+ cancelAnimation();
+
+ final View dialogContainer = layout.getDialogContainer();
+ mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
+ if (mDialogAnimation == null) {
+ endCallback.run();
+ return;
+ }
+ mDialogAnimation.setAnimationListener(getAnimationListener(
+ /* startCallback= */ () -> {},
+ /* endCallback= */ () -> {
+ dialogContainer.setAlpha(0);
+ mDialogAnimation = null;
+ endCallback.run();
+ }));
+
+ mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0,
+ mDialogAnimation.getDuration());
+ mBackgroundDimAnimator.addListener(getDimAnimatorListener());
+
+ dialogContainer.startAnimation(mDialogAnimation);
+ mBackgroundDimAnimator.start();
+ }
+
+ /**
+ * Cancels all animations and resets the state of the controller.
+ */
+ void cancelAnimation() {
+ if (mDialogAnimation != null) {
+ mDialogAnimation.cancel();
+ mDialogAnimation = null;
+ }
+ if (mBackgroundDimAnimator != null) {
+ mBackgroundDimAnimator.cancel();
+ mBackgroundDimAnimator = null;
+ }
+ }
+
+ private Animation loadAnimation(int animAttr) {
+ Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
+ animAttr, /* translucent= */ false);
+ if (animation == null) {
+ Log.e(TAG, "Failed to load animation " + animAttr);
+ }
+ return animation;
+ }
+
+ private Animation.AnimationListener getAnimationListener(Runnable startCallback,
+ Runnable endCallback) {
+ return new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ startCallback.run();
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ endCallback.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ };
+ }
+
+ private AnimatorListenerAdapter getDimAnimatorListener() {
+ return new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundDimAnimator = null;
+ }
+ };
+ }
+
+ private static Animator getAlphaAnimator(
+ Drawable drawable, int endAlpha, long duration) {
+ Animator animator = ObjectAnimator.ofInt(drawable, DRAWABLE_ALPHA, endAlpha);
+ animator.setDuration(duration);
+ return animator;
+ }
+
+ private static final Property<Drawable, Integer> DRAWABLE_ALPHA = new IntProperty<Drawable>(
+ "alpha") {
+ @Override
+ public void setValue(Drawable object, int value) {
+ object.setAlpha(value);
+ }
+
+ @Override
+ public Integer get(Drawable object) {
+ return object.getAlpha();
+ }
+ };
+}
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/letterboxedu/LetterboxEduDialogActionLayout.java
new file mode 100644
index 000000000000..02197f644a39
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
@@ -0,0 +1,67 @@
+/*
+ * 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.compatui.letterboxedu;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Custom layout for Letterbox Education dialog action.
+ */
+class LetterboxEduDialogActionLayout extends FrameLayout {
+
+ public LetterboxEduDialogActionLayout(Context context) {
+ this(context, null);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray styledAttributes =
+ context.getTheme().obtainStyledAttributes(
+ attrs, R.styleable.LetterboxEduDialogActionLayout, defStyleAttr,
+ defStyleRes);
+ int iconId = styledAttributes.getResourceId(
+ R.styleable.LetterboxEduDialogActionLayout_icon, 0);
+ String text = styledAttributes.getString(
+ R.styleable.LetterboxEduDialogActionLayout_text);
+ styledAttributes.recycle();
+
+ View rootView = inflate(getContext(), R.layout.letterbox_education_dialog_action_layout,
+ this);
+ ((ImageView) rootView.findViewById(
+ R.id.letterbox_education_dialog_action_icon)).setImageResource(iconId);
+ ((TextView) rootView.findViewById(R.id.letterbox_education_dialog_action_text)).setText(
+ text);
+ }
+}
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/letterboxedu/LetterboxEduDialogLayout.java
new file mode 100644
index 000000000000..2e0b09e9d230
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for Letterbox Education Dialog and background dim.
+ *
+ * <p>This layout should fill the entire task and the background around the dialog acts as the
+ * background dim which dismisses the dialog when clicked.
+ */
+class LetterboxEduDialogLayout extends ConstraintLayout {
+
+ // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
+ // 204 is simply 255 * 0.8.
+ static final int BACKGROUND_DIM_ALPHA = 204;
+
+ private View mDialogContainer;
+ private TextView mDialogTitle;
+ private Drawable mBackgroundDim;
+
+ public LetterboxEduDialogLayout(Context context) {
+ this(context, null);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ View getDialogContainer() {
+ return mDialogContainer;
+ }
+
+ TextView getDialogTitle() {
+ return mDialogTitle;
+ }
+
+ Drawable getBackgroundDim() {
+ return mBackgroundDim;
+ }
+
+ /**
+ * Register a callback for the dismiss button and background dim.
+ *
+ * @param callback The callback to register or null if all on click listeners should be removed.
+ */
+ void setDismissOnClickListener(@Nullable Runnable callback) {
+ final OnClickListener listener = callback == null ? null : view -> callback.run();
+ findViewById(R.id.letterbox_education_dialog_dismiss_button).setOnClickListener(listener);
+ // Clicks on the background dim should also dismiss the dialog.
+ setOnClickListener(listener);
+ // We add a no-op on-click listener to the dialog container so that clicks on it won't
+ // propagate to the listener of the layout (which represents the background dim).
+ mDialogContainer.setOnClickListener(callback == null ? null : view -> {});
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDialogContainer = findViewById(R.id.letterbox_education_dialog_container);
+ mDialogTitle = findViewById(R.id.letterbox_education_dialog_title);
+ mBackgroundDim = getBackground().mutate();
+ // Set the alpha of the background dim to 0 for enter animation.
+ mBackgroundDim.setAlpha(0);
+ }
+}
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/letterboxedu/LetterboxEduWindowManager.java
new file mode 100644
index 000000000000..35f1038a6853
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -0,0 +1,260 @@
+/*
+ * 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.compatui.letterboxedu;
+
+import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+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.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Window manager for the Letterbox Education.
+ */
+public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
+
+ /**
+ * The Letterbox Education should be the topmost child of the Task in case there can be more
+ * than one child.
+ */
+ 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 LetterboxEduAnimationController mAnimationController;
+
+ private final Transitions mTransitions;
+
+ /**
+ * The id of the current user, to associate with a boolean in {@link
+ * #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}, indicating whether that user has already seen the
+ * Letterbox Education dialog.
+ */
+ private final int mUserId;
+
+ // Remember the last reported state in case visibility changes due to keyguard or IME updates.
+ private boolean mEligibleForLetterboxEducation;
+
+ @Nullable
+ @VisibleForTesting
+ LetterboxEduDialogLayout mLayout;
+
+ private final Runnable mOnDismissCallback;
+
+ /**
+ * The vertical margin between the dialog container and the task stable bounds (excluding
+ * insets).
+ */
+ private final int mDialogVerticalMargin;
+
+ public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions,
+ Runnable onDismissCallback) {
+ this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
+ onDismissCallback, new LetterboxEduAnimationController(context));
+ }
+
+ @VisibleForTesting
+ LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
+ LetterboxEduAnimationController animationController) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ 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);
+ }
+
+ @Override
+ protected int getZOrder() {
+ return Z_ORDER;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ // - If taskbar education is showing, the letterbox education shouldn't be shown for the
+ // given task until the taskbar education is dismissed and the compat info changes (then
+ // 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());
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ updateDialogMargins();
+
+ // startEnterAnimation will be called immediately if shell-transitions are disabled.
+ mTransitions.runOnIdle(this::startEnterAnimation);
+
+ return mLayout;
+ }
+
+ private void updateDialogMargins() {
+ if (mLayout == null) {
+ return;
+ }
+ final View dialogContainer = mLayout.getDialogContainer();
+ MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams();
+
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
+ marginParams.bottomMargin =
+ taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
+ dialogContainer.setLayoutParams(marginParams);
+ }
+
+ private LetterboxEduDialogLayout inflateLayout() {
+ return (LetterboxEduDialogLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.letterbox_education_dialog_layout, null);
+ }
+
+ private void startEnterAnimation() {
+ if (mLayout == null) {
+ // Dialog has already been released.
+ return;
+ }
+ mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
+ this::onDialogEnterAnimationEnded);
+ }
+
+ private void onDialogEnterAnimationEnded() {
+ if (mLayout == null) {
+ // 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);
+ }
+
+ private void onDismiss() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setDismissOnClickListener(null);
+ mAnimationController.startExitAnimation(mLayout, () -> {
+ release();
+ mOnDismissCallback.run();
+ });
+ }
+
+ @Override
+ public void release() {
+ mAnimationController.cancelAnimation();
+ super.release();
+ }
+
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
+
+ return super.updateCompatInfo(taskInfo, taskListener, canShow);
+ }
+
+ @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);
+ updateDialogMargins();
+ relayout(windowLayoutParams);
+ }
+
+ @Override
+ protected void updateSurfacePosition() {
+ // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+ // of the task (parent surface), which is the default position of a surface.
+ }
+
+ @Override
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
+ final Rect taskBounds = getTaskBounds();
+ return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+ 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(),
+ LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
+ }
+}
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 711a0ac76702..1dd5ebcd993e 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
@@ -27,11 +27,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.Pip;
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.PipMediaController;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -39,6 +36,8 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
+import com.android.wm.shell.pip.tv.TvPipBoundsState;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
@@ -60,29 +59,33 @@ public abstract class TvPipModule {
@Provides
static Optional<Pip> providePip(
Context context,
- PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsState tvPipBoundsState,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
PipTransitionController pipTransitionController,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
+ DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
return Optional.of(
TvPipController.create(
context,
- pipBoundsState,
- pipBoundsAlgorithm,
+ tvPipBoundsState,
+ tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
pipMediaController,
tvPipNotificationController,
taskStackListener,
+ displayController,
windowManagerShellWrapper,
- mainExecutor));
+ mainExecutor,
+ mainHandler));
}
@WMSingleton
@@ -93,15 +96,15 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
- static PipBoundsAlgorithm providePipBoundsAlgorithm(Context context,
- PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
+ static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
+ TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
+ return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm);
}
@WMSingleton
@Provides
- static PipBoundsState providePipBoundsState(Context context) {
- return new PipBoundsState(context);
+ static TvPipBoundsState provideTvPipBoundsState(Context context) {
+ return new TvPipBoundsState(context);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -109,21 +112,22 @@ public abstract class TvPipModule {
@Provides
static PipTransitionController provideTvPipTransition(
Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
- PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
- return new TvPipTransition(pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+ PipAnimationController pipAnimationController,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ TvPipBoundsState tvPipBoundsState, TvPipMenuController pipMenuController) {
+ return new TvPipTransition(tvPipBoundsState, pipMenuController,
+ tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
@WMSingleton
@Provides
static TvPipMenuController providesTvPipMenuController(
Context context,
- PipBoundsState pipBoundsState,
+ TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows,
PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
- return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController,
+ return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
mainHandler);
}
@@ -154,21 +158,20 @@ public abstract class TvPipModule {
static PipTaskOrganizer providePipTaskOrganizer(Context context,
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
- PipBoundsState pipBoundsState,
+ TvPipBoundsState tvPipBoundsState,
PipTransitionState pipTransitionState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<LegacySplitScreenController> splitScreenOptional,
- Optional<SplitScreenController> newSplitScreenOptional,
+ Optional<SplitScreenController> splitScreenControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, splitScreenOptional, newSplitScreenOptional,
+ pipTransitionController, splitScreenControllerOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
}
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 6d4b2fa60b55..026eeb09d741 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
@@ -36,9 +36,12 @@ import com.android.wm.shell.ShellInitImpl;
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.apppairs.AppPairs;
import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
@@ -65,6 +68,7 @@ import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
@@ -92,6 +96,7 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import java.util.Optional;
import dagger.BindsOptionalOf;
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -165,8 +170,8 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static DragAndDrop provideDragAndDrop(DragAndDropController dragAndDropController) {
- return dragAndDropController.asDragAndDrop();
+ static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
+ return Optional.of(dragAndDropController.asDragAndDrop());
}
@WMSingleton
@@ -181,8 +186,22 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static CompatUI provideCompatUI(CompatUIController compatUIController) {
- return compatUIController.asCompatUI();
+ static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
+ Context context,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasksOptional
+ ) {
+ return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
+ displayController, displayInsetsController, recentTasksOptional);
+ }
+
+ @WMSingleton
+ @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
+ return Optional.of(compatUIController.asCompatUI());
}
@WMSingleton
@@ -190,9 +209,9 @@ public abstract class WMShellBaseModule {
static CompatUIController provideCompatUIController(Context context,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
return new CompatUIController(context, displayController, displayInsetsController,
- imeController, syncQueue, mainExecutor);
+ imeController, syncQueue, mainExecutor, transitionsLazy);
}
@WMSingleton
@@ -237,6 +256,17 @@ public abstract class WMShellBaseModule {
}
//
+ // Back animation
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<BackAnimation> provideBackAnimation(
+ Optional<BackAnimationController> backAnimationController) {
+ return backAnimationController.map(BackAnimationController::getBackAnimationImpl);
+ }
+
+ //
// Bubbles (optional feature)
//
@@ -253,15 +283,24 @@ public abstract class WMShellBaseModule {
// Fullscreen
//
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract FullscreenTaskListener optionalFullscreenTaskListener();
+
@WMSingleton
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
+ @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
- Optional<RecentTasksController> recentTasksOptional
- ) {
- return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
- recentTasksOptional);
+ Optional<RecentTasksController> recentTasksOptional) {
+ if (fullscreenTaskListener.isPresent()) {
+ return fullscreenTaskListener.get();
+ } else {
+ return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
+ recentTasksOptional);
+ }
}
//
@@ -352,7 +391,6 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
-
//
// Task to Surface communication
//
@@ -449,9 +487,16 @@ public abstract class WMShellBaseModule {
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
DisplayController displayController, Context context,
@ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, displayController, context, mainExecutor,
- animExecutor);
+ mainHandler, animExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static TaskViewTransitions provideTaskViewTransitions(Transitions transitions) {
+ return new TaskViewTransitions(transitions);
}
//
@@ -585,8 +630,10 @@ public abstract class WMShellBaseModule {
static TaskViewFactoryController provideTaskViewFactoryController(
ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
- SyncTransactionQueue syncQueue) {
- return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue);
+ SyncTransactionQueue syncQueue,
+ TaskViewTransitions taskViewTransitions) {
+ return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue,
+ taskViewTransitions);
}
//
@@ -606,6 +653,7 @@ public abstract class WMShellBaseModule {
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
@@ -622,6 +670,7 @@ public abstract class WMShellBaseModule {
displayInsetsController,
dragAndDropController,
shellTaskOrganizer,
+ kidsModeTaskOrganizer,
bubblesOptional,
splitScreenOptional,
appPairsOptional,
@@ -649,6 +698,7 @@ public abstract class WMShellBaseModule {
@Provides
static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
@@ -657,8 +707,21 @@ public abstract class WMShellBaseModule {
Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer,
+ return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static Optional<BackAnimationController> provideBackAnimationController(
+ Context context,
+ @ShellMainThread ShellExecutor shellExecutor
+ ) {
+ if (BackAnimationController.IS_ENABLED) {
+ return Optional.of(
+ new BackAnimationController(shellExecutor, context));
+ }
+ return Optional.empty();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 5c205f97beb7..8f9636c0bb30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -27,7 +27,10 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
+import androidx.annotation.Nullable;
+
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
@@ -35,7 +38,6 @@ import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.R;
import dagger.Module;
import dagger.Provides;
@@ -53,7 +55,7 @@ public abstract class WMShellConcurrencyModule {
/**
* Returns whether to enable a separate shell thread for the shell features.
*/
- private static boolean enableShellMainThread(Context context) {
+ public static boolean enableShellMainThread(Context context) {
return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
}
@@ -85,23 +87,41 @@ public abstract class WMShellConcurrencyModule {
}
/**
+ * Creates a shell main thread to be injected into the shell components. This does not provide
+ * the {@param HandleThread}, but is used to create the thread prior to initializing the
+ * WM component, and is explicitly bound.
+ *
+ * See {@link com.android.systemui.SystemUIFactory#init(Context, boolean)}.
+ */
+ public static HandlerThread createShellMainThread() {
+ HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
+ return mainThread;
+ }
+
+ /**
* Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
* multiple types of messages, etc.)
+ *
+ * @param mainThread If non-null, this thread is expected to be started already
*/
@WMSingleton
@Provides
@ShellMainThread
public static Handler provideShellMainHandler(Context context,
+ @Nullable @ShellMainThread HandlerThread mainThread,
@ExternalMainThread Handler sysuiMainHandler) {
if (enableShellMainThread(context)) {
- HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
- mainThread.start();
- if (Build.IS_DEBUGGABLE) {
- mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
- mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
- MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
- }
- return Handler.createAsync(mainThread.getLooper());
+ if (mainThread == null) {
+ // If this thread wasn't pre-emptively started, then create and start it
+ mainThread = createShellMainThread();
+ mainThread.start();
+ }
+ if (Build.IS_DEBUGGABLE) {
+ mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return Handler.createAsync(mainThread.getLooper());
}
return sysuiMainHandler;
}
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 f562fd9cf1af..73f393140cbc 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
@@ -22,11 +22,13 @@ import android.content.pm.LauncherApps;
import android.os.Handler;
import android.view.WindowManager;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
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.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
@@ -106,13 +108,16 @@ public class WMShellModule {
UiEventLogger uiEventLogger,
ShellTaskOrganizer organizer,
DisplayController displayController,
+ @DynamicOverride Optional<OneHandedController> oneHandedOptional,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
+ TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
return BubbleController.create(context, null /* synchronizer */,
floatingContentCoordinator, statusBarService, windowManager,
windowManagerShellWrapper, launcherApps, taskStackListener,
- uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue);
+ uiEventLogger, organizer, displayController, oneHandedOptional,
+ mainExecutor, mainHandler, taskViewTransitions, syncQueue);
}
//
@@ -139,12 +144,10 @@ public class WMShellModule {
static OneHandedController provideOneHandedController(Context context,
WindowManager windowManager, DisplayController displayController,
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, windowManager,
- displayController, displayLayout, taskStackListener, uiEventLogger, mainExecutor,
- mainHandler);
+ UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor,
+ @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
+ return OneHandedController.create(context, windowManager, displayController, displayLayout,
+ taskStackListener, jankMonitor, uiEventLogger, mainExecutor, mainHandler);
}
//
@@ -159,13 +162,14 @@ public class WMShellModule {
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
+ DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
- rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
+ rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
recentTasks, stageTaskUnfoldControllerProvider);
}
@@ -242,10 +246,11 @@ public class WMShellModule {
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
SystemWindows systemWindows,
Optional<SplitScreenController> splitScreenOptional,
+ PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, splitScreenOptional, mainExecutor, mainHandler);
+ systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
}
@WMSingleton
@@ -280,15 +285,14 @@ public class WMShellModule {
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
PipTransitionController pipTransitionController,
- Optional<LegacySplitScreenController> splitScreenOptional,
- Optional<SplitScreenController> newSplitScreenOptional,
+ Optional<SplitScreenController> splitScreenControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, splitScreenOptional, newSplitScreenOptional,
+ pipTransitionController, splitScreenControllerOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@@ -305,9 +309,12 @@ public class WMShellModule {
Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
- PhonePipMenuController pipMenuController) {
+ PhonePipMenuController pipMenuController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreenController> splitScreenOptional) {
return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+ pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer,
+ pipSurfaceTransactionHelper, splitScreenOptional);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 101295d246bc..11ecc9197be7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,6 +35,9 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
@@ -54,6 +57,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -205,6 +209,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
+ pd.dragLayout.update(event);
break;
case ACTION_DRAG_LOCATION:
pd.dragLayout.update(event);
@@ -250,10 +255,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
// Hide the window if another drag hasn't been started while animating the drop
setDropTargetWindowVisibility(pd, View.INVISIBLE);
}
-
- // Clean up the drag surface
- mTransaction.reparent(dragSurface, null);
- mTransaction.apply();
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 8e6c05d83906..756831007c35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -45,10 +45,12 @@ import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.ResolveInfo;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
@@ -62,8 +64,11 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -105,12 +110,26 @@ public class DragAndDropPolicy {
*/
void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
- mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
+ mSession = new DragSession(mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
}
/**
+ * Returns the last running task.
+ */
+ ActivityManager.RunningTaskInfo getLatestRunningTask() {
+ return mSession.runningTaskInfo;
+ }
+
+ /**
+ * Returns the number of targets.
+ */
+ int getNumTargets() {
+ return mTargets.size();
+ }
+
+ /**
* Returns the target's regions based on the current state of the device and display.
*/
@NonNull
@@ -132,6 +151,8 @@ public class DragAndDropPolicy {
final Rect fullscreenHitRegion = new Rect(displayRegion);
final boolean inLandscape = mSession.displayLayout.isLandscape();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
+ final float dividerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.split_divider_bar_width);
// We allow splitting if we are already in split-screen or the running task is a standard
// task in fullscreen mode.
final boolean allowSplit = inSplitScreen
@@ -147,37 +168,45 @@ public class DragAndDropPolicy {
if (inLandscape) {
final Rect leftHitRegion = new Rect();
- final Rect leftDrawRegion = topOrLeftBounds;
final Rect rightHitRegion = new Rect();
- final Rect rightDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- leftHitRegion.set(topOrLeftBounds);
- rightHitRegion.set(bottomOrRightBounds);
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ leftHitRegion.set(displayRegion);
+ leftHitRegion.right = (int) centerX;
+ rightHitRegion.set(displayRegion);
+ rightHitRegion.left = (int) centerX;
} else {
displayRegion.splitVertically(leftHitRegion, rightHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
} else {
final Rect topHitRegion = new Rect();
- final Rect topDrawRegion = topOrLeftBounds;
final Rect bottomHitRegion = new Rect();
- final Rect bottomDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- topHitRegion.set(topOrLeftBounds);
- bottomHitRegion.set(bottomOrRightBounds);
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ topHitRegion.set(displayRegion);
+ topHitRegion.bottom = (int) centerX;
+ bottomHitRegion.set(displayRegion);
+ bottomHitRegion.top = (int) centerX;
} else {
displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
}
} else {
// Split-screen not allowed, so only show the fullscreen target
@@ -237,32 +266,68 @@ public class DragAndDropPolicy {
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
- mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
- null, position, opts);
+ final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position),
+ position, opts);
+ }
+ }
+
+ /**
+ * Returns the fill-in intent to use when starting an app from a drop.
+ */
+ @VisibleForTesting
+ Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) {
+ // Get the drag app
+ final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */);
+ final ComponentName dragIntentActivity = !infos.isEmpty()
+ ? infos.get(0).activityInfo.getComponentName()
+ : null;
+
+ // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen)
+ final boolean inSplitScreen = mSplitScreen != null
+ && mSplitScreen.isSplitScreenVisible();
+ final ComponentName currentActivity;
+ if (!inSplitScreen) {
+ currentActivity = mSession.runningTaskInfo != null
+ ? mSession.runningTaskInfo.baseActivity
+ : null;
+ } else {
+ final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : SPLIT_POSITION_TOP_OR_LEFT;
+ ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
+ mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
+ currentActivity = nonReplacedTaskInfo.baseActivity;
}
+
+ if (currentActivity.equals(dragIntentActivity)) {
+ // Only apply MULTIPLE_TASK if we are dragging the same activity
+ final Intent fillInIntent = new Intent();
+ fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK");
+ return fillInIntent;
+ }
+ return null;
}
/**
* Per-drag session data.
*/
private static class DragSession {
- private final Context mContext;
private final ActivityTaskManager mActivityTaskManager;
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
Intent dragData;
- int runningTaskId;
+ ActivityManager.RunningTaskInfo runningTaskInfo;
@WindowConfiguration.WindowingMode
int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
- boolean runningTaskIsResizeable;
boolean dragItemSupportsSplitscreen;
- DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
- mContext = context;
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
displayLayout = dispLayout;
@@ -276,10 +341,9 @@ public class DragAndDropPolicy {
mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
final ActivityManager.RunningTaskInfo task = tasks.get(0);
+ runningTaskInfo = task;
runningTaskWinMode = task.getWindowingMode();
runningTaskActType = task.getActivityType();
- runningTaskId = task.taskId;
- runningTaskIsResizeable = task.isResizeable;
}
final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index f98849260511..25fe8b9e0da3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.draganddrop;
import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -24,10 +25,9 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.content.ClipData;
import android.content.Context;
@@ -36,7 +36,6 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -47,12 +46,12 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.ArrayList;
-import java.util.List;
/**
* Coordinates the visible drop targets for the current drag.
@@ -139,6 +138,12 @@ public class DragLayout extends LinearLayout {
}
}
+ private void updateContainerMarginsForSingleTask() {
+ mDropZoneView1.setContainerMargin(
+ mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
+ mDropZoneView2.setContainerMargin(0, 0, 0, 0);
+ }
+
private void updateContainerMargins(int orientation) {
final float halfMargin = mDisplayMargin / 2f;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -154,10 +159,6 @@ public class DragLayout extends LinearLayout {
}
}
- public boolean hasDropTarget() {
- return mCurrentTarget != null;
- }
-
public boolean hasDropped() {
return mHasDropped;
}
@@ -171,22 +172,22 @@ public class DragLayout extends LinearLayout {
boolean alreadyInSplit = mSplitScreenController != null
&& mSplitScreenController.isSplitScreenVisible();
if (!alreadyInSplit) {
- List<ActivityManager.RunningTaskInfo> tasks = null;
- // Figure out the splashscreen info for the existing task.
- try {
- tasks = ActivityTaskManager.getService().getTasks(1,
- false /* filterOnlyVisibleRecents */,
- false /* keepIntentExtra */);
- } catch (RemoteException e) {
- // don't show an icon / will just use the defaults
- }
- if (tasks != null && !tasks.isEmpty()) {
- ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
- Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
- int bgColor1 = getResizingBackgroundColor(taskInfo1);
- mDropZoneView1.setAppInfo(bgColor1, icon1);
- mDropZoneView2.setAppInfo(bgColor1, icon1);
- updateDropZoneSizes(null, null); // passing null splits the views evenly
+ ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+ if (taskInfo1 != null) {
+ final int activityType = taskInfo1.getActivityType();
+ if (activityType == ACTIVITY_TYPE_STANDARD) {
+ Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
+ int bgColor1 = getResizingBackgroundColor(taskInfo1);
+ mDropZoneView1.setAppInfo(bgColor1, icon1);
+ mDropZoneView2.setAppInfo(bgColor1, icon1);
+ updateDropZoneSizes(null, null); // passing null splits the views evenly
+ } else {
+ // We use the first drop zone to show the fullscreen highlight, and don't need
+ // to set additional info
+ mDropZoneView1.setForceIgnoreBottomMargin(true);
+ updateDropZoneSizesForSingleTask();
+ updateContainerMarginsForSingleTask();
+ }
}
} else {
// We're already in split so get taskInfo from the controller to populate icon / color.
@@ -212,6 +213,21 @@ public class DragLayout extends LinearLayout {
}
}
+ private void updateDropZoneSizesForSingleTask() {
+ final LinearLayout.LayoutParams dropZoneView1 =
+ (LayoutParams) mDropZoneView1.getLayoutParams();
+ final LinearLayout.LayoutParams dropZoneView2 =
+ (LayoutParams) mDropZoneView2.getLayoutParams();
+ dropZoneView1.width = MATCH_PARENT;
+ dropZoneView1.height = MATCH_PARENT;
+ dropZoneView2.width = 0;
+ dropZoneView2.height = 0;
+ dropZoneView1.weight = 1;
+ dropZoneView2.weight = 0;
+ mDropZoneView1.setLayoutParams(dropZoneView1);
+ mDropZoneView2.setLayoutParams(dropZoneView2);
+ }
+
/**
* Sets the size of the two drop zones based on the provided bounds. The divider sits between
* the views and its size is included in the calculations.
@@ -269,6 +285,9 @@ public class DragLayout extends LinearLayout {
* Updates the visible drop target as the user drags.
*/
public void update(DragEvent event) {
+ if (mHasDropped) {
+ return;
+ }
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
@@ -279,12 +298,16 @@ public class DragLayout extends LinearLayout {
// Animating to no target
animateSplitContainers(false, null /* animCompleteCallback */);
} else if (mCurrentTarget == null) {
- // Animating to first target
- animateSplitContainers(true, null /* animCompleteCallback */);
- animateHighlight(target);
+ if (mPolicy.getNumTargets() == 1) {
+ animateFullscreenContainer(true);
+ } else {
+ animateSplitContainers(true, null /* animCompleteCallback */);
+ animateHighlight(target);
+ }
} else {
// Switching between targets
- animateHighlight(target);
+ mDropZoneView1.animateSwitch();
+ mDropZoneView2.animateSwitch();
}
mCurrentTarget = target;
}
@@ -296,6 +319,10 @@ public class DragLayout extends LinearLayout {
public void hide(DragEvent event, Runnable hideCompleteCallback) {
mIsShowing = false;
animateSplitContainers(false, hideCompleteCallback);
+ // Reset the state if we previously force-ignore the bottom margin
+ mDropZoneView1.setForceIgnoreBottomMargin(false);
+ mDropZoneView2.setForceIgnoreBottomMargin(false);
+ updateContainerMargins(getResources().getConfiguration().orientation);
mCurrentTarget = null;
}
@@ -310,18 +337,70 @@ public class DragLayout extends LinearLayout {
// Process the drop
mPolicy.handleDrop(mCurrentTarget, event.getClipData());
- // TODO(b/169894807): Coordinate with dragSurface
+ // Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
+ hideDragSurface(dragSurface);
return handledDrop;
}
+ private void hideDragSurface(SurfaceControl dragSurface) {
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
+ // Currently the splash icon animation runs with the default ValueAnimator duration of
+ // 300ms
+ dragSurfaceAnimator.setDuration(300);
+ dragSurfaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ dragSurfaceAnimator.addUpdateListener(animation -> {
+ float t = animation.getAnimatedFraction();
+ float alpha = 1f - t;
+ // TODO: Scale the drag surface as well once we make all the source surfaces
+ // consistent
+ tx.setAlpha(dragSurface, alpha);
+ tx.apply();
+ });
+ dragSurfaceAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cleanUpSurface();
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ // Already handled above
+ return;
+ }
+ cleanUpSurface();
+ }
+
+ private void cleanUpSurface() {
+ // Clean up the drag surface
+ tx.remove(dragSurface);
+ tx.apply();
+ }
+ });
+ dragSurfaceAnimator.start();
+ }
+
+ private void animateFullscreenContainer(boolean visible) {
+ mStatusBarManager.disable(visible
+ ? HIDE_STATUS_BAR_FLAGS
+ : DISABLE_NONE);
+ // We're only using the first drop zone if there is one fullscreen target
+ mDropZoneView1.setShowingMargin(visible);
+ mDropZoneView1.setShowingHighlight(visible);
+ }
+
private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
mStatusBarManager.disable(visible
? HIDE_STATUS_BAR_FLAGS
: DISABLE_NONE);
mDropZoneView1.setShowingMargin(visible);
mDropZoneView2.setShowingMargin(visible);
- ObjectAnimator animator = mDropZoneView1.getAnimator();
+ Animator animator = mDropZoneView1.getAnimator();
if (animCompleteCallback != null) {
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@@ -341,17 +420,11 @@ public class DragLayout extends LinearLayout {
if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
- mDropZoneView1.setShowingSplash(false);
-
mDropZoneView2.setShowingHighlight(false);
- mDropZoneView2.setShowingSplash(true);
} else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
mDropZoneView1.setShowingHighlight(false);
- mDropZoneView1.setShowingSplash(true);
-
mDropZoneView2.setShowingHighlight(true);
- mDropZoneView2.setShowingSplash(false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 2f47af57d496..38870bcb3383 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
+import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -27,7 +28,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -43,8 +43,8 @@ import com.android.wm.shell.R;
*/
public class DropZoneView extends FrameLayout {
- private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
- private static final int HIGHLIGHT_ALPHA_INT = 255;
+ private static final float SPLASHSCREEN_ALPHA = 0.90f;
+ private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
@@ -61,54 +61,28 @@ public class DropZoneView extends FrameLayout {
}
};
- private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
- new IntProperty<ColorDrawable>("splashscreen") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
- private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
- new IntProperty<ColorDrawable>("highlight") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
private final Path mPath = new Path();
private final float[] mContainerMargin = new float[4];
private float mCornerRadius;
private float mBottomInset;
+ private boolean mIgnoreBottomMargin;
private int mMarginColor; // i.e. color used for negative space like the container insets
- private int mHighlightColor;
private boolean mShowingHighlight;
private boolean mShowingSplash;
private boolean mShowingMargin;
- // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
- private ObjectAnimator mSplashAnimator;
- private ObjectAnimator mHighlightAnimator;
+ private int mSplashScreenColor;
+ private int mHighlightColor;
+
+ private ObjectAnimator mBackgroundAnimator;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
// Renders a highlight or neutral transparent color
- private ColorDrawable mDropZoneDrawable;
+ private ColorDrawable mColorDrawable;
// Renders the translucent splashscreen with the app icon in the middle
private ImageView mSplashScreenView;
- private ColorDrawable mSplashBackgroundDrawable;
// Renders the margin / insets around the dropzone container
private MarginView mMarginView;
@@ -130,19 +104,14 @@ public class DropZoneView extends FrameLayout {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mMarginColor = getResources().getColor(R.color.taskbar_background);
- mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
-
- mDropZoneDrawable = new ColorDrawable();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(0);
- setBackgroundDrawable(mDropZoneDrawable);
+ int c = getResources().getColor(android.R.color.system_accent1_500);
+ mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
+ mColorDrawable = new ColorDrawable();
+ setBackgroundDrawable(mColorDrawable);
mSplashScreenView = new ImageView(context);
mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
- mSplashBackgroundDrawable = new ColorDrawable();
- mSplashBackgroundDrawable.setColor(Color.WHITE);
- mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
- mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mSplashScreenView.setAlpha(0f);
@@ -157,10 +126,6 @@ public class DropZoneView extends FrameLayout {
mMarginColor = getResources().getColor(R.color.taskbar_background);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
- final int alpha = mDropZoneDrawable.getAlpha();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(alpha);
-
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
@@ -177,6 +142,14 @@ public class DropZoneView extends FrameLayout {
}
}
+ /** Ignores the bottom margin provided by the insets. */
+ public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
+ mIgnoreBottomMargin = ignoreBottomMargin;
+ if (mMarginPercent > 0) {
+ mMarginView.invalidate();
+ }
+ }
+
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
mBottomInset = bottom;
@@ -187,38 +160,39 @@ public class DropZoneView extends FrameLayout {
}
/** Sets the color and icon to use for the splashscreen when shown. */
- public void setAppInfo(int splashScreenColor, Drawable appIcon) {
- mSplashBackgroundDrawable.setColor(splashScreenColor);
+ public void setAppInfo(int color, Drawable appIcon) {
+ Color c = Color.valueOf(color);
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
mSplashScreenView.setImageDrawable(appIcon);
}
/** @return an active animator for this view if one exists. */
@Nullable
- public ObjectAnimator getAnimator() {
+ public Animator getAnimator() {
if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
return mMarginAnimator;
- } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
- return mHighlightAnimator;
- } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
- return mSplashAnimator;
+ } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
+ return mBackgroundAnimator;
}
return null;
}
- /** Animates the splashscreen to show or hide. */
- public void setShowingSplash(boolean showingSplash) {
- if (mShowingSplash != showingSplash) {
- mShowingSplash = showingSplash;
- animateSplashToState();
- }
+ /** Animates between highlight and splashscreen depending on current state. */
+ public void animateSwitch() {
+ mShowingHighlight = !mShowingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(mColorDrawable.getColor(), newColor);
+ animateSplashScreenIcon();
}
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
- if (mShowingHighlight != showingHighlight) {
- mShowingHighlight = showingHighlight;
- animateHighlightToState();
- }
+ mShowingHighlight = showingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(Color.TRANSPARENT, newColor);
+ animateSplashScreenIcon();
}
/** Animates the margins around the drop zone to show or hide. */
@@ -228,38 +202,29 @@ public class DropZoneView extends FrameLayout {
animateMarginToState();
}
if (!mShowingMargin) {
- setShowingHighlight(false);
- setShowingSplash(false);
+ mShowingHighlight = false;
+ mShowingSplash = false;
+ animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
+ animateSplashScreenIcon();
}
}
- private void animateSplashToState() {
- if (mSplashAnimator != null) {
- mSplashAnimator.cancel();
+ private void animateBackground(int startColor, int endColor) {
+ if (mBackgroundAnimator != null) {
+ mBackgroundAnimator.cancel();
}
- mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
- SPLASHSCREEN_ALPHA,
- mSplashBackgroundDrawable.getAlpha(),
- mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
- if (!mShowingSplash) {
- mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
+ "color",
+ startColor,
+ endColor);
+ if (!mShowingSplash && !mShowingHighlight) {
+ mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
- mSplashAnimator.start();
- mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
+ mBackgroundAnimator.start();
}
- private void animateHighlightToState() {
- if (mHighlightAnimator != null) {
- mHighlightAnimator.cancel();
- }
- mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
- HIGHLIGHT_ALPHA,
- mDropZoneDrawable.getAlpha(),
- mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
- if (!mShowingHighlight) {
- mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- }
- mHighlightAnimator.start();
+ private void animateSplashScreenIcon() {
+ mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
}
private void animateMarginToState() {
@@ -301,7 +266,8 @@ public class DropZoneView extends FrameLayout {
mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
mContainerMargin[1] * mMarginPercent,
getWidth() - (mContainerMargin[2] * mMarginPercent),
- getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset,
+ getHeight() - (mContainerMargin[3] * mMarginPercent)
+ - (mIgnoreBottomMargin ? 0 : mBottomInset),
mCornerRadius * mMarginPercent,
mCornerRadius * mMarginPercent,
Path.Direction.CW);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 52ff21bc3172..fef9be36a35f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -110,6 +110,24 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
+ if (!mTasks.contains(taskId)) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ return mTasks.get(taskId).mLeash;
+ }
+
+ @Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + this);
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 6e38e421d4b6..73e6cba43ec0 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
@@ -133,10 +133,20 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
if (!mDataByTaskId.contains(taskId)) {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
- b.setParent(mDataByTaskId.get(taskId).surface);
+ return mDataByTaskId.get(taskId).surface;
}
@Override
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
new file mode 100644
index 000000000000..003c55977841
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -0,0 +1,337 @@
+/*
+ * 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.kidsmode;
+
+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.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.startingsurface.StartingWindowController;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A dedicated task organizer when kids mode is enabled.
+ * - Creates a root task with bounds that exclude the navigation bar area
+ * - Launch all task into the root task except for Launcher
+ */
+public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
+ private static final String TAG = "KidsModeTaskOrganizer";
+
+ private static final int[] CONTROLLED_ACTIVITY_TYPES =
+ {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
+ private static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+
+ private final Handler mMainHandler;
+ private final Context mContext;
+ private final SyncTransactionQueue mSyncQueue;
+ private final DisplayController mDisplayController;
+ private final DisplayInsetsController mDisplayInsetsController;
+
+ @VisibleForTesting
+ ActivityManager.RunningTaskInfo mLaunchRootTask;
+ @VisibleForTesting
+ SurfaceControl mLaunchRootLeash;
+ @VisibleForTesting
+ final IBinder mCookie = new Binder();
+
+ private final InsetsState mInsetsState = new InsetsState();
+ private int mDisplayWidth;
+ private int mDisplayHeight;
+
+ private ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver;
+ private boolean mEnabled;
+
+ DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final int displayWidth = displayLayout.width();
+ final int displayHeight = displayLayout.height();
+ if (displayWidth == mDisplayWidth || displayHeight == mDisplayHeight) {
+ return;
+ }
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ updateBounds();
+ }
+ };
+
+ DisplayInsetsController.OnInsetsChangedListener mOnInsetsChangedListener =
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ // Update bounds only when the insets of navigation bar or task bar is changed.
+ if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR),
+ mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR))
+ && Objects.equals(insetsState.peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR),
+ mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) {
+ return;
+ }
+ mInsetsState.set(insetsState);
+ updateBounds();
+ }
+ };
+
+ @VisibleForTesting
+ KidsModeTaskOrganizer(
+ ITaskOrganizerController taskOrganizerController,
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Context context,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasks,
+ ForceShowNavigationBarSettingsObserver forceShowNavigationBarSettingsObserver) {
+ super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks);
+ mContext = context;
+ mMainHandler = mainHandler;
+ mSyncQueue = syncTransactionQueue;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mForceShowNavigationBarSettingsObserver = forceShowNavigationBarSettingsObserver;
+ }
+
+ public KidsModeTaskOrganizer(
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Context context,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasks) {
+ super(mainExecutor, context, /* compatUI= */ null, recentTasks);
+ mContext = context;
+ mMainHandler = mainHandler;
+ mSyncQueue = syncTransactionQueue;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ }
+
+ /**
+ * Initializes kids mode status.
+ */
+ public void initialize(StartingWindowController startingWindowController) {
+ initStartingWindow(startingWindowController);
+ if (mForceShowNavigationBarSettingsObserver == null) {
+ mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver(
+ mMainHandler, mContext);
+ }
+ mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState());
+ updateKidsModeState();
+ mForceShowNavigationBarSettingsObserver.register();
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mEnabled && mLaunchRootTask == null && taskInfo.launchCookies != null
+ && taskInfo.launchCookies.contains(mCookie)) {
+ mLaunchRootTask = taskInfo;
+ mLaunchRootLeash = leash;
+ updateTask();
+ }
+ super.onTaskAppeared(taskInfo, leash);
+
+ mSyncQueue.runInSync(t -> {
+ // Reset several properties back to fullscreen (PiP, for example, leaves all these
+ // properties in a bad state).
+ t.setCrop(leash, null);
+ t.setPosition(leash, 0, 0);
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ });
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mLaunchRootTask != null && mLaunchRootTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mLaunchRootTask)) {
+ mLaunchRootTask = taskInfo;
+ }
+
+ super.onTaskInfoChanged(taskInfo);
+ }
+
+ @VisibleForTesting
+ void updateKidsModeState() {
+ final boolean enabled = mForceShowNavigationBarSettingsObserver.isEnabled();
+ if (mEnabled == enabled) {
+ return;
+ }
+ mEnabled = enabled;
+ if (mEnabled) {
+ enable();
+ } else {
+ disable();
+ }
+ }
+
+ @VisibleForTesting
+ void enable() {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout != null) {
+ mDisplayWidth = displayLayout.width();
+ mDisplayHeight = displayLayout.height();
+ }
+ mInsetsState.set(mDisplayController.getInsetsState(DEFAULT_DISPLAY));
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY,
+ mOnInsetsChangedListener);
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ List<TaskAppearedInfo> taskAppearedInfos = registerOrganizer();
+ for (int i = 0; i < taskAppearedInfos.size(); i++) {
+ final TaskAppearedInfo info = taskAppearedInfos.get(i);
+ onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
+ createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, mCookie);
+ updateTask();
+ }
+
+ @VisibleForTesting
+ void disable() {
+ mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
+ mOnInsetsChangedListener);
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ updateTask();
+ final WindowContainerToken token = mLaunchRootTask.token;
+ if (token != null) {
+ deleteRootTask(token);
+ }
+ mLaunchRootTask = null;
+ mLaunchRootLeash = null;
+ unregisterOrganizer();
+ }
+
+ private void updateTask() {
+ updateTask(getWindowContainerTransaction());
+ }
+
+ private void updateTask(WindowContainerTransaction wct) {
+ if (mLaunchRootTask == null || mLaunchRootLeash == null) {
+ return;
+ }
+ final Rect taskBounds = calculateBounds();
+ final WindowContainerToken rootToken = mLaunchRootTask.token;
+ wct.setBounds(rootToken, mEnabled ? taskBounds : null);
+ wct.setLaunchRoot(rootToken,
+ mEnabled ? CONTROLLED_WINDOWING_MODES : null,
+ mEnabled ? CONTROLLED_ACTIVITY_TYPES : null);
+ wct.reparentTasks(
+ mEnabled ? null : rootToken /* currentParent */,
+ mEnabled ? rootToken : null /* newParent */,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */);
+ wct.reorder(rootToken, mEnabled /* onTop */);
+ mSyncQueue.queue(wct);
+ final SurfaceControl rootLeash = mLaunchRootLeash;
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
+ t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+ });
+ }
+
+ private Rect calculateBounds() {
+ final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
+ final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR);
+ final InsetsSource taskBarSource = mInsetsState.peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ if (navBarSource != null && !navBarSource.getFrame().isEmpty()) {
+ bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
+ } else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) {
+ bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
+ } else {
+ bounds.setEmpty();
+ }
+ return bounds;
+ }
+
+ private void updateBounds() {
+ if (mLaunchRootTask == null) {
+ return;
+ }
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ final Rect taskBounds = calculateBounds();
+ wct.setBounds(mLaunchRootTask.token, taskBounds);
+ mSyncQueue.queue(wct);
+ final SurfaceControl finalLeash = mLaunchRootLeash;
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
+ t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
+ });
+ }
+
+ @VisibleForTesting
+ WindowContainerTransaction getWindowContainerTransaction() {
+ return new WindowContainerTransaction();
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + " mEnabled=" + mEnabled);
+ pw.println(innerPrefix + " mLaunchRootTask=" + mLaunchRootTask);
+ pw.println(innerPrefix + " mLaunchRootLeash=" + mLaunchRootLeash);
+ pw.println(innerPrefix + " mDisplayWidth=" + mDisplayWidth);
+ pw.println(innerPrefix + " mDisplayHeight=" + mDisplayHeight);
+ pw.println(innerPrefix + " mInsetsState=" + mInsetsState);
+ super.dump(pw, innerPrefix);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 86bf3ff1cea9..d2f42c39acd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -343,10 +343,20 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
if (!mLeashByTaskId.contains(taskId)) {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
- b.setParent(mLeashByTaskId.get(taskId));
+ return mLeashByTaskId.get(taskId);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
new file mode 100644
index 000000000000..c20b7d9b2747
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -0,0 +1,246 @@
+/*
+ * 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.onehanded;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Holds view hierarchy of a root surface and helps inflate a themeable view for background.
+ */
+public final class BackgroundWindowManager extends WindowlessWindowManager {
+ private static final String TAG = BackgroundWindowManager.class.getSimpleName();
+ private static final int THEME_COLOR_OFFSET = 10;
+
+ private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mTransactionFactory;
+
+ private Context mContext;
+ private Rect mDisplayBounds;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mLeash;
+ private View mBackgroundView;
+ private @OneHandedState.State int mCurrentState;
+
+ public BackgroundWindowManager(Context context) {
+ super(context.getResources().getConfiguration(), null /* rootSurface */,
+ null /* hostInputToken */);
+ mContext = context;
+ mTransactionFactory = SurfaceControl.Transaction::new;
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl(IWindow window) {
+ return super.getSurfaceControl(window);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ /**
+ * onConfigurationChanged events for updating background theme color.
+ */
+ public void onConfigurationChanged() {
+ if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
+ updateThemeOnly();
+ }
+ }
+
+ /**
+ * One-handed mode state changed callback
+ * @param newState of One-handed mode representing by {@link OneHandedState}
+ */
+ public void onStateChanged(int newState) {
+ mCurrentState = newState;
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setColorLayer()
+ .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height())
+ .setFormat(PixelFormat.RGB_888)
+ .setOpaque(true)
+ .setName(TAG)
+ .setCallsite("BackgroundWindowManager#attachToParentSurface");
+ mLeash = builder.build();
+ b.setParent(mLeash);
+ }
+
+ /** Inflates background view on to the root surface. */
+ boolean initView() {
+ if (mBackgroundView != null || mViewHost != null) {
+ return false;
+ }
+
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ mBackgroundView = (View) LayoutInflater.from(mContext)
+ .inflate(R.layout.background_panel, null /* root */);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
+ | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.setTitle("background-panel");
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+ mViewHost.setView(mBackgroundView, lp);
+ return true;
+ }
+
+ /**
+ * Called when onDisplayAdded() or onDisplayRemoved() callback.
+ * @param displayLayout The latest {@link DisplayLayout} for display bounds.
+ */
+ public void onDisplayChanged(DisplayLayout displayLayout) {
+ // One-handed mode is only available on portrait.
+ if (displayLayout.height() > displayLayout.width()) {
+ mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
+ } else {
+ mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
+ }
+ }
+
+ private void updateThemeOnly() {
+ if (mBackgroundView == null || mViewHost == null || mLeash == null) {
+ Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to "
+ + "update theme only!");
+ return;
+ }
+
+ WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
+ mBackgroundView.getLayoutParams();
+ mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+ mViewHost.setView(mBackgroundView, lp);
+ }
+
+ /**
+ * Shows the background layer when One-handed mode triggered.
+ */
+ public void showBackgroundLayer() {
+ if (!initView()) {
+ updateThemeOnly();
+ return;
+ }
+ if (mLeash == null) {
+ Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode "
+ + "background panel!");
+ return;
+ }
+
+ mTransactionFactory.getTransaction()
+ .setAlpha(mLeash, 1.0f)
+ .setLayer(mLeash, -1 /* at bottom-most layer */)
+ .show(mLeash)
+ .apply();
+ }
+
+ /**
+ * Remove the leash of background layer after stop One-handed mode.
+ */
+ public void removeBackgroundLayer() {
+ if (mBackgroundView != null) {
+ mBackgroundView = null;
+ }
+
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ mTransactionFactory.getTransaction().remove(mLeash).apply();
+ mLeash = null;
+ }
+ }
+
+ /**
+ * Gets {@link SurfaceControl} of the background layer.
+ * @return {@code null} if not exist.
+ */
+ @Nullable
+ SurfaceControl getSurfaceControl() {
+ return mLeash;
+ }
+
+ private int getThemeColor() {
+ final Context themedContext = new ContextThemeWrapper(mContext,
+ com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+ return themedContext.getColor(R.color.one_handed_tutorial_background_color);
+ }
+
+ int getThemeColorForBackground() {
+ final int origThemeColor = getThemeColor();
+ return android.graphics.Color.argb(Color.alpha(origThemeColor),
+ Color.red(origThemeColor) - THEME_COLOR_OFFSET,
+ Color.green(origThemeColor) - THEME_COLOR_OFFSET,
+ Color.blue(origThemeColor) - THEME_COLOR_OFFSET);
+ }
+
+ private float adjustColor(int origColor) {
+ return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
+ }
+
+ void dump(@NonNull PrintWriter pw) {
+ final String innerPrefix = " ";
+ pw.println(TAG);
+ pw.print(innerPrefix + "mDisplayBounds=");
+ pw.println(mDisplayBounds);
+ pw.print(innerPrefix + "mViewHost=");
+ pw.println(mViewHost);
+ pw.print(innerPrefix + "mLeash=");
+ pw.println(mLeash);
+ pw.print(innerPrefix + "mBackgroundView=");
+ pw.println(mBackgroundView);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 3253bb06c835..b00182f36cc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.onehanded;
import android.content.res.Configuration;
+import android.os.SystemProperties;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -26,6 +27,9 @@ import com.android.wm.shell.common.annotations.ExternalThread;
@ExternalThread
public interface OneHanded {
+ boolean sIsSupportOneHandedMode = SystemProperties.getBoolean(
+ OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
+
/**
* Returns a binder that can be passed to an external process to manipulate OneHanded.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
deleted file mode 100644
index 9e1c61aac868..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ /dev/null
@@ -1,272 +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.onehanded;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.view.ContextThemeWrapper;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.animation.LinearInterpolator;
-import android.window.DisplayAreaAppearedInfo;
-import android.window.DisplayAreaInfo;
-import android.window.DisplayAreaOrganizer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Manages OneHanded color background layer areas.
- * To avoid when turning the Dark theme on, users can not clearly identify
- * the screen has entered one handed mode.
- */
-public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
- implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener {
- private static final String TAG = "OneHandedBackgroundPanelOrganizer";
- private static final int THEME_COLOR_OFFSET = 10;
- private static final int ALPHA_ANIMATION_DURATION = 200;
-
- private final Context mContext;
- private final SurfaceSession mSurfaceSession = new SurfaceSession();
- private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
- mTransactionFactory;
-
- private @OneHandedState.State int mCurrentState;
- private ValueAnimator mAlphaAnimator;
-
- private float mTranslationFraction;
- private float[] mThemeColor;
-
- /**
- * The background to distinguish the boundary of translated windows and empty region when
- * one handed mode triggered.
- */
- private Rect mBkgBounds;
- private Rect mStableInsets;
-
- @Nullable
- @VisibleForTesting
- SurfaceControl mBackgroundSurface;
- @Nullable
- private SurfaceControl mParentLeash;
-
- public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
- OneHandedSettingsUtil settingsUtil, Executor executor) {
- super(executor);
- mContext = context;
- mTranslationFraction = settingsUtil.getTranslationFraction(context);
- mTransactionFactory = SurfaceControl.Transaction::new;
- updateThemeColors();
- }
-
- @Override
- public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
- @NonNull SurfaceControl leash) {
- mParentLeash = leash;
- }
-
- @Override
- public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
- final List<DisplayAreaAppearedInfo> displayAreaInfos;
- displayAreaInfos = super.registerOrganizer(displayAreaFeature);
- for (int i = 0; i < displayAreaInfos.size(); i++) {
- final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
- onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
- }
- return displayAreaInfos;
- }
-
- @Override
- public void unregisterOrganizer() {
- super.unregisterOrganizer();
- removeBackgroundPanelLayer();
- mParentLeash = null;
- }
-
- @Override
- public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) {
- final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos);
- tx.setPosition(mBackgroundSurface, 0, yTopPos);
- }
-
- @Nullable
- @VisibleForTesting
- boolean isRegistered() {
- return mParentLeash != null;
- }
-
- void createBackgroundSurface() {
- mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
- .setBufferSize(mBkgBounds.width(), mBkgBounds.height())
- .setColorLayer()
- .setFormat(PixelFormat.RGB_888)
- .setOpaque(true)
- .setName("one-handed-background-panel")
- .setCallsite("OneHandedBackgroundPanelOrganizer")
- .build();
-
- // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
- mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
- mAlphaAnimator.setInterpolator(new LinearInterpolator());
- mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION);
- mAlphaAnimator.addUpdateListener(
- animator -> detachBackgroundFromParent(animator));
- }
-
- void detachBackgroundFromParent(ValueAnimator animator) {
- if (mBackgroundSurface == null || mParentLeash == null) {
- return;
- }
- // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
- final float currentValue = (float) animator.getAnimatedValue();
- final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
- if (currentValue == 0.0f) {
- tx.reparent(mBackgroundSurface, null).apply();
- } else {
- tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply();
- }
- }
-
- /**
- * Called when onDisplayAdded() or onDisplayRemoved() callback.
- *
- * @param displayLayout The latest {@link DisplayLayout} representing current displayId
- */
- public void onDisplayChanged(DisplayLayout displayLayout) {
- mStableInsets = displayLayout.stableInsets();
- // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
- if (displayLayout.height() > displayLayout.width()) {
- mBkgBounds = new Rect(0, 0, displayLayout.width(),
- Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top);
- } else {
- mBkgBounds = new Rect(0, 0, displayLayout.height(),
- Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top);
- }
- }
-
- @VisibleForTesting
- void onStart() {
- if (mBackgroundSurface == null) {
- createBackgroundSurface();
- }
- showBackgroundPanelLayer();
- }
-
- /**
- * Called when transition finished.
- */
- public void onStopFinished() {
- if (mAlphaAnimator == null) {
- return;
- }
- mAlphaAnimator.start();
- }
-
- @VisibleForTesting
- void showBackgroundPanelLayer() {
- if (mParentLeash == null) {
- return;
- }
-
- if (mBackgroundSurface == null) {
- createBackgroundSurface();
- }
-
- // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
- if (mAlphaAnimator.isRunning()) {
- mAlphaAnimator.end();
- }
-
- mTransactionFactory.getTransaction()
- .reparent(mBackgroundSurface, mParentLeash)
- .setAlpha(mBackgroundSurface, 1.0f)
- .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
- .setColor(mBackgroundSurface, mThemeColor)
- .show(mBackgroundSurface)
- .apply();
- }
-
- @VisibleForTesting
- void removeBackgroundPanelLayer() {
- if (mBackgroundSurface == null) {
- return;
- }
-
- mTransactionFactory.getTransaction()
- .remove(mBackgroundSurface)
- .apply();
- mBackgroundSurface = null;
- }
-
- /**
- * onConfigurationChanged events for updating tutorial text.
- */
- public void onConfigurationChanged() {
- updateThemeColors();
-
- if (mCurrentState != STATE_ACTIVE) {
- return;
- }
- showBackgroundPanelLayer();
- }
-
- private void updateThemeColors() {
- final Context themedContext = new ContextThemeWrapper(mContext,
- com.android.internal.R.style.Theme_DeviceDefault_DayNight);
- final int themeColor = themedContext.getColor(
- R.color.one_handed_tutorial_background_color);
- mThemeColor = new float[]{
- adjustColor(Color.red(themeColor)),
- adjustColor(Color.green(themeColor)),
- adjustColor(Color.blue(themeColor))};
- }
-
- private float adjustColor(int origColor) {
- return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
- }
-
- @Override
- public void onStateChanged(int newState) {
- mCurrentState = newState;
- }
-
- void dump(@NonNull PrintWriter pw) {
- final String innerPrefix = " ";
- pw.println(TAG);
- pw.print(innerPrefix + "mBackgroundSurface=");
- pw.println(mBackgroundSurface);
- pw.print(innerPrefix + "mBkgBounds=");
- pw.println(mBkgBounds);
- pw.print(innerPrefix + "mThemeColor=");
- pw.println(mThemeColor);
- pw.print(innerPrefix + "mTranslationFraction=");
- pw.println(mTranslationFraction);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0686146e821..48acfc1c76e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.onehanded;
-import static android.os.UserHandle.USER_CURRENT;
import static android.os.UserHandle.myUserId;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -30,12 +29,10 @@ import android.annotation.BinderThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.om.IOverlayManager;
-import android.content.om.OverlayInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -48,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
@@ -70,9 +68,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
"persist.debug.one_handed_offset_percentage";
- private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY =
- "com.android.internal.systemui.onehanded.gestural";
- private static final int OVERLAY_ENABLED_DELAY_MS = 250;
private static final int DISPLAY_AREA_READY_RETRY_MS = 10;
public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
@@ -104,7 +99,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private OneHandedEventCallback mEventCallback;
private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
- private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
private OneHandedUiEventLogger mOneHandedUiEventLogger;
private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -168,7 +162,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
public void onStopFinished(Rect bounds) {
mState.setState(STATE_NONE);
notifyShortcutStateChanged(STATE_NONE);
- mBackgroundPanelOrganizer.onStopFinished();
}
};
@@ -200,37 +193,34 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
public static OneHandedController create(
Context context, WindowManager windowManager, DisplayController displayController,
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) {
+ InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
+ ShellExecutor mainExecutor, Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
- OneHandedState transitionState = new OneHandedState();
+ OneHandedState oneHandedState = new OneHandedState();
+ BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
- settingsUtil, windowManager);
+ settingsUtil, windowManager, backgroundWindowManager);
OneHandedAnimationController animationController =
new OneHandedAnimationController(context);
OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
mainExecutor);
- OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
- new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil,
- mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
context, displayLayout, settingsUtil, animationController, tutorialHandler,
- oneHandedBackgroundPanelOrganizer, mainExecutor);
+ jankMonitor, mainExecutor);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
- return new OneHandedController(context, displayController,
- oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
- settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
- oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
- mainHandler);
+ return new OneHandedController(context, displayController, organizer, touchHandler,
+ tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState,
+ jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener,
+ mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
DisplayController displayController,
- OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
OneHandedTouchHandler touchHandler,
OneHandedTutorialHandler tutorialHandler,
@@ -238,6 +228,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
OneHandedAccessibilityUtil oneHandedAccessibilityUtil,
OneHandedTimeoutHandler timeoutHandler,
OneHandedState state,
+ InteractionJankMonitor jankMonitor,
OneHandedUiEventLogger uiEventsLogger,
IOverlayManager overlayManager,
TaskStackListenerImpl taskStackListener,
@@ -246,7 +237,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mContext = context;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
- mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
mDisplayController = displayController;
mTouchHandler = touchHandler;
@@ -282,14 +272,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
setupCallback();
registerSettingObservers(mUserId);
setupTimeoutListener();
- setupGesturalOverlay();
updateSettings();
+ updateDisplayLayout(mContext.getDisplayId());
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
- mState.addSListeners(mBackgroundPanelOrganizer);
mState.addSListeners(mTutorialHandler);
}
@@ -371,7 +360,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mOneHandedAccessibilityUtil.announcementForScreenReader(
mOneHandedAccessibilityUtil.getOneHandedStartDescription());
- mBackgroundPanelOrganizer.onStart();
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
mOneHandedUiEventLogger.writeEvent(
@@ -399,8 +387,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mEventCallback = callback;
}
- @VisibleForTesting
- void registerTransitionCallback(OneHandedTransitionCallback callback) {
+ /**
+ * Registers {@link OneHandedTransitionCallback} to monitor the transition status
+ */
+ public void registerTransitionCallback(OneHandedTransitionCallback callback) {
mDisplayAreaOrganizer.registerTransitionCallback(callback);
}
@@ -453,11 +443,15 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
onShortcutEnabledChanged();
}
- private void updateDisplayLayout(int displayId) {
+ @VisibleForTesting
+ void updateDisplayLayout(int displayId) {
final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
+ if (newDisplayLayout == null) {
+ Slog.w(TAG, "Failed to get new DisplayLayout.");
+ return;
+ }
mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
mTutorialHandler.onDisplayChanged(newDisplayLayout);
- mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout);
}
private ContentObserver getObserver(Runnable onChangeRunnable) {
@@ -517,11 +511,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
: OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
setOneHandedEnabled(enabled);
-
- // Also checks swipe to notification settings since they all need gesture overlay.
- setEnabledGesturalOverlay(
- enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContext.getContentResolver(), mUserId), true /* DelayExecute */);
}
@VisibleForTesting
@@ -586,7 +575,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
if (!mIsOneHandedEnabled) {
mDisplayAreaOrganizer.unregisterOrganizer();
- mBackgroundPanelOrganizer.unregisterOrganizer();
// Do NOT register + unRegister DA in the same call
return;
}
@@ -595,45 +583,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mDisplayAreaOrganizer.registerOrganizer(
OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
}
-
- if (!mBackgroundPanelOrganizer.isRegistered()) {
- mBackgroundPanelOrganizer.registerOrganizer(
- OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
- }
- }
-
- private void setupGesturalOverlay() {
- if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContext.getContentResolver(), mUserId)) {
- return;
- }
-
- OverlayInfo info = null;
- try {
- mOverlayManager.setHighestPriority(ONE_HANDED_MODE_GESTURAL_OVERLAY, USER_CURRENT);
- info = mOverlayManager.getOverlayInfo(ONE_HANDED_MODE_GESTURAL_OVERLAY, USER_CURRENT);
- } catch (RemoteException e) { /* Do nothing */ }
-
- if (info != null && !info.isEnabled()) {
- // Enable the default gestural one handed overlay.
- setEnabledGesturalOverlay(true /* enabled */, false /* delayExecute */);
- }
- }
-
- @VisibleForTesting
- private void setEnabledGesturalOverlay(boolean enabled, boolean delayExecute) {
- if (mState.isTransitioning() || delayExecute) {
- // Enabled overlay package may affect the current animation(e.g:Settings switch),
- // so we delay 250ms to enabled overlay after switch animation finish, only delay once.
- mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(enabled, false),
- OVERLAY_ENABLED_DELAY_MS);
- return;
- }
- try {
- mOverlayManager.setEnabled(ONE_HANDED_MODE_GESTURAL_OVERLAY, enabled, USER_CURRENT);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
}
@VisibleForTesting
@@ -648,13 +597,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
private void onConfigChanged(Configuration newConfig) {
- if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) {
+ if (mTutorialHandler == null) {
return;
}
if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
return;
}
- mBackgroundPanelOrganizer.onConfigurationChanged();
mTutorialHandler.onConfigurationChanged();
}
@@ -685,10 +633,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
pw.println(mIsSwipeToNotificationEnabled);
- if (mBackgroundPanelOrganizer != null) {
- mBackgroundPanelOrganizer.dump(pw);
- }
-
if (mDisplayAreaOrganizer != null) {
mDisplayAreaOrganizer.dump(pw);
}
@@ -714,19 +658,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver(), mUserId);
-
- if (mOverlayManager != null) {
- OverlayInfo info = null;
- try {
- info = mOverlayManager.getOverlayInfo(ONE_HANDED_MODE_GESTURAL_OVERLAY,
- USER_CURRENT);
- } catch (RemoteException e) { /* Do nothing */ }
-
- if (info != null && !info.isEnabled()) {
- pw.print(innerPrefix + "OverlayInfo=");
- pw.println(info);
- }
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 1b2f4768110b..f61d1b95bd85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,12 +16,15 @@
package com.android.wm.shell.onehanded;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
@@ -34,6 +37,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -41,6 +45,7 @@ import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Manages OneHanded display areas such as offset.
@@ -62,6 +67,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private final Rect mLastVisualDisplayBounds = new Rect();
private final Rect mDefaultDisplayBounds = new Rect();
private final OneHandedSettingsUtil mOneHandedSettingsUtil;
+ private final InteractionJankMonitor mJankMonitor;
+ private final Context mContext;
private boolean mIsReady;
private float mLastVisualOffset = 0;
@@ -73,7 +80,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
mSurfaceControlTransactionFactory;
private OneHandedTutorialHandler mTutorialHandler;
private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
- private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
@VisibleForTesting
OneHandedAnimationCallback mOneHandedAnimationCallback =
@@ -95,7 +101,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
public void onOneHandedAnimationEnd(SurfaceControl.Transaction tx,
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
+ final boolean isEntering = animator.getTransitionDirection()
+ == TRANSITION_DIRECTION_TRIGGER;
if (mAnimationController.isAnimatorsConsumed()) {
+ endCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION
+ : CUJ_ONE_HANDED_EXIT_TRANSITION);
finishOffset((int) animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -105,7 +115,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
public void onOneHandedAnimationCancel(
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
+ final boolean isEntering = animator.getTransitionDirection()
+ == TRANSITION_DIRECTION_TRIGGER;
if (mAnimationController.isAnimatorsConsumed()) {
+ cancelCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION
+ : CUJ_ONE_HANDED_EXIT_TRANSITION);
finishOffset((int) animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -120,20 +134,20 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
OneHandedSettingsUtil oneHandedSettingsUtil,
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
- OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
+ InteractionJankMonitor jankMonitor,
ShellExecutor mainExecutor) {
super(mainExecutor);
- mDisplayLayout.set(displayLayout);
+ mContext = context;
+ setDisplayLayout(displayLayout);
mOneHandedSettingsUtil = oneHandedSettingsUtil;
- updateDisplayBounds();
mAnimationController = animationController;
+ mJankMonitor = jankMonitor;
final int animationDurationConfig = context.getResources().getInteger(
R.integer.config_one_handed_translate_animation_duration);
mEnterExitAnimationDurationMs =
SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
animationDurationConfig);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
- mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer;
mTutorialHandler = tutorialHandler;
}
@@ -198,6 +212,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
final int direction = yOffset > 0
? TRANSITION_DIRECTION_TRIGGER
: TRANSITION_DIRECTION_EXIT;
+ if (direction == TRANSITION_DIRECTION_TRIGGER) {
+ beginCUJTracing(CUJ_ONE_HANDED_ENTER_TRANSITION, "enterOneHanded");
+ } else {
+ beginCUJTracing(CUJ_ONE_HANDED_EXIT_TRANSITION, "stopOneHanded");
+ }
mDisplayAreaTokenMap.forEach(
(token, leash) -> {
animateWindows(token, leash, fromPos, yOffset, direction,
@@ -236,7 +255,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
animator.setTransitionDirection(direction)
.addOneHandedAnimationCallback(mOneHandedAnimationCallback)
.addOneHandedAnimationCallback(mTutorialHandler)
- .addOneHandedAnimationCallback(mBackgroundPanelOrganizer)
.setDuration(durationMs)
.start();
}
@@ -282,6 +300,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
@VisibleForTesting
void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
+ updateDisplayBounds();
}
@VisibleForTesting
@@ -289,6 +308,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreaTokenMap;
}
+ @VisibleForTesting
void updateDisplayBounds() {
mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
mLastVisualDisplayBounds.set(mDefaultDisplayBounds);
@@ -301,6 +321,26 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
mTransitionCallbacks.add(callback);
}
+ void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) {
+ final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
+ getDisplayAreaTokenMap().entrySet().iterator().next();
+ final InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ cujType, mContext, firstEntry.getValue());
+ if (!TextUtils.isEmpty(tag)) {
+ builder.setTag(tag);
+ }
+ mJankMonitor.begin(builder);
+ }
+
+ void endCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ mJankMonitor.end(cujType);
+ }
+
+ void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ mJankMonitor.cancel(cujType);
+ }
+
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index 81dd60d715e9..04e8cf9d2c44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -64,6 +64,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
private final float mTutorialHeightRatio;
private final WindowManager mWindowManager;
+ private final BackgroundWindowManager mBackgroundWindowManager;
private @OneHandedState.State int mCurrentState;
private int mTutorialAreaHeight;
@@ -78,9 +79,10 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
private int mAlphaAnimationDurationMs;
public OneHandedTutorialHandler(Context context, OneHandedSettingsUtil settingsUtil,
- WindowManager windowManager) {
+ WindowManager windowManager, BackgroundWindowManager backgroundWindowManager) {
mContext = context;
mWindowManager = windowManager;
+ mBackgroundWindowManager = backgroundWindowManager;
mTutorialHeightRatio = settingsUtil.getTranslationFraction(context);
mAlphaAnimationDurationMs = settingsUtil.getTransitionDuration(context);
}
@@ -109,8 +111,19 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
}
@Override
+ public void onStartFinished(Rect bounds) {
+ fillBackgroundColor();
+ }
+
+ @Override
+ public void onStopFinished(Rect bounds) {
+ removeBackgroundSurface();
+ }
+
+ @Override
public void onStateChanged(int newState) {
mCurrentState = newState;
+ mBackgroundWindowManager.onStateChanged(newState);
switch (newState) {
case STATE_ENTERING:
createViewAndAttachToWindow(mContext);
@@ -125,7 +138,6 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
case STATE_NONE:
checkTransitionEnd();
removeTutorialFromWindowManager();
- break;
default:
break;
}
@@ -145,6 +157,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
}
mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio);
mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION;
+ mBackgroundWindowManager.onDisplayChanged(displayLayout);
}
@VisibleForTesting
@@ -168,6 +181,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
private void attachTargetToWindow() {
try {
mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams());
+ mBackgroundWindowManager.showBackgroundLayer();
} catch (IllegalStateException e) {
// This shouldn't happen, but if the target is already added, just update its
// layout params.
@@ -185,6 +199,11 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
mTargetViewContainer = null;
}
+ @VisibleForTesting
+ void removeBackgroundSurface() {
+ mBackgroundWindowManager.removeBackgroundLayer();
+ }
+
/**
* Returns layout params for the dismiss target, using the latest display metrics.
*/
@@ -212,9 +231,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
* onConfigurationChanged events for updating tutorial text.
*/
public void onConfigurationChanged() {
+ mBackgroundWindowManager.onConfigurationChanged();
+
removeTutorialFromWindowManager();
if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
createViewAndAttachToWindow(mContext);
+ fillBackgroundColor();
updateThemeColor();
checkTransitionEnd();
}
@@ -246,6 +268,14 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
tutorialDesc.setTextColor(themedTextColorSecondary);
}
+ private void fillBackgroundColor() {
+ if (mTargetViewContainer == null || mBackgroundWindowManager == null) {
+ return;
+ }
+ mTargetViewContainer.setBackgroundColor(
+ mBackgroundWindowManager.getThemeColorForBackground());
+ }
+
private void setupAlphaTransition(boolean isEntering) {
final float start = isEntering ? 0.0f : 1.0f;
final float end = isEntering ? 1.0f : 0.0f;
@@ -281,5 +311,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
pw.println(mAlphaTransitionStart);
pw.print(innerPrefix + "mAlphaAnimationDurationMs=");
pw.println(mAlphaAnimationDurationMs);
+
+ if (mBackgroundWindowManager != null) {
+ mBackgroundWindowManager.dump(pw);
+ }
}
}
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 ddc85f758916..e03421dd58ac 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
@@ -47,12 +47,13 @@ interface IPip {
/**
* Notifies the swiping Activity to PiP onto home transition is finished
*
+ * @param taskId the Task id that the Activity and overlay are currently in.
* @param componentName ComponentName represents the Activity
* @param destinationBounds the destination bounds the PiP window lands into
* @param overlay an optional overlay to fade out after entering PiP
*/
- oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds,
- in SurfaceControl overlay) = 2;
+ oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
+ in Rect destinationBounds, in SurfaceControl overlay) = 2;
/**
* Sets listener to get pinned stack animation callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
index b4c745fc4892..ef627647794e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
@@ -32,4 +32,9 @@ oneway interface IPipAnimationListener {
* @param cornerRadius the pixel value of the corner radius, zero means it's disabled.
*/
void onPipCornerRadiusChanged(int cornerRadius);
+
+ /**
+ * Notifies the listener that user leaves PiP by tapping on the expand button.
+ */
+ void onExpandPip();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index b3b1ba7cd1c1..87eca74acd0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -72,9 +72,10 @@ public class PinnedStackListenerForwarder {
}
}
- private void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ private void onActionsChanged(ParceledListSlice<RemoteAction> actions,
+ RemoteAction closeAction) {
for (PinnedTaskListener listener : mListeners) {
- listener.onActionsChanged(actions);
+ listener.onActionsChanged(actions, closeAction);
}
}
@@ -90,6 +91,12 @@ public class PinnedStackListenerForwarder {
}
}
+ private void onExpandedAspectRatioChanged(float aspectRatio) {
+ for (PinnedTaskListener listener : mListeners) {
+ listener.onExpandedAspectRatioChanged(aspectRatio);
+ }
+ }
+
@BinderThread
private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub {
@Override
@@ -107,9 +114,10 @@ public class PinnedStackListenerForwarder {
}
@Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
+ RemoteAction closeAction) {
mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onActionsChanged(actions);
+ PinnedStackListenerForwarder.this.onActionsChanged(actions, closeAction);
});
}
@@ -126,6 +134,15 @@ public class PinnedStackListenerForwarder {
PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
});
}
+
+ @Override
+ public void onExpandedAspectRatioChanged(float aspectRatio) {
+ mMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onExpandedAspectRatioChanged(aspectRatio);
+ });
+ }
+
+
}
/**
@@ -137,10 +154,13 @@ public class PinnedStackListenerForwarder {
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {}
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
+ RemoteAction closeAction) {}
public void onActivityHidden(ComponentName componentName) {}
public void onAspectRatioChanged(float aspectRatio) {}
+
+ public void onExpandedAspectRatioChanged(float aspectRatio) {}
}
}
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 9575b0a720bc..77fd228af286 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
@@ -584,9 +584,11 @@ public class PipAnimationController {
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
if (isOutPipDirection) {
- getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
+ getSurfaceTransactionHelper().crop(tx, leash, end)
+ .scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle)
+ getSurfaceTransactionHelper().crop(tx, leash, base)
+ .scale(tx, leash, base, bounds, angle)
.round(tx, leash, base, bounds);
}
} else {
@@ -618,17 +620,17 @@ public class PipAnimationController {
setCurrentValue(bounds);
final Rect insets = computeInsets(fraction);
final float degree, x, y;
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (Transitions.SHELL_TRANSITIONS_ROTATION) {
if (rotationDelta == ROTATION_90) {
degree = 90 * (1 - fraction);
x = fraction * (end.left - start.left)
- + start.left + start.right * (1 - fraction);
+ + start.left + start.width() * (1 - fraction);
y = fraction * (end.top - start.top) + start.top;
} else {
degree = -90 * (1 - fraction);
x = fraction * (end.left - start.left) + start.left;
y = fraction * (end.top - start.top)
- + start.top + start.bottom * (1 - fraction);
+ + start.top + start.height() * (1 - fraction);
}
} else {
if (rotationDelta == ROTATION_90) {
@@ -646,8 +648,10 @@ public class PipAnimationController {
getSurfaceTransactionHelper()
.rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
insets, degree, x, y, isOutPipDirection,
- rotationDelta == ROTATION_270 /* clockwise */)
- .round(tx, leash, sourceBounds, bounds);
+ rotationDelta == ROTATION_270 /* clockwise */);
+ if (shouldApplyCornerRadius()) {
+ getSurfaceTransactionHelper().round(tx, leash, sourceBounds, bounds);
+ }
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 a4b866aa3f5e..7397e5273753 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
@@ -32,6 +32,7 @@ import android.util.Size;
import android.util.TypedValue;
import android.view.Gravity;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
@@ -56,7 +57,7 @@ public class PipBoundsAlgorithm {
private int mDefaultStackGravity;
private int mDefaultMinSize;
private int mOverridableMinSize;
- private Point mScreenEdgeInsets;
+ protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
@@ -76,15 +77,15 @@ public class PipBoundsAlgorithm {
private void reloadResources(Context context) {
final Resources res = context.getResources();
mDefaultAspectRatio = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
+ R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity);
+ R.integer.config_defaultPictureInPictureGravity);
mDefaultMinSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+ R.dimen.default_minimal_size_pip_resizable_task);
mOverridableMinSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task);
+ R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
+ R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
? Size.parseSize(screenEdgeInsetsDpString)
: null;
@@ -96,9 +97,9 @@ public class PipBoundsAlgorithm {
mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
mDefaultSizePercent = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
+ R.dimen.config_pictureInPictureDefaultSizePercent);
mMaxAspectRatioForMinSize = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+ R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
@@ -193,7 +194,7 @@ public class PipBoundsAlgorithm {
public float getAspectRatioOrDefault(
@android.annotation.Nullable PictureInPictureParams params) {
return params != null && params.hasSetAspectRatio()
- ? params.getAspectRatio()
+ ? params.getAspectRatioFloat()
: getDefaultAspectRatio();
}
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 b3558ad4b91e..17d7f5d0d567 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
@@ -20,20 +20,24 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
+import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.util.Log;
+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;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -41,20 +45,25 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
/**
* Singleton source of truth for the current state of PIP bounds.
*/
-public final class PipBoundsState {
+public class PipBoundsState {
public static final int STASH_TYPE_NONE = 0;
public static final int STASH_TYPE_LEFT = 1;
public static final int STASH_TYPE_RIGHT = 2;
+ public static final int STASH_TYPE_BOTTOM = 3;
+ public static final int STASH_TYPE_TOP = 4;
@IntDef(prefix = { "STASH_TYPE_" }, value = {
STASH_TYPE_NONE,
STASH_TYPE_LEFT,
- STASH_TYPE_RIGHT
+ STASH_TYPE_RIGHT,
+ STASH_TYPE_BOTTOM,
+ STASH_TYPE_TOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface StashType {}
@@ -88,6 +97,24 @@ public final class PipBoundsState {
private int mShelfHeight;
/** Whether the user has resized the PIP manually. */
private boolean mHasUserResizedPip;
+ /**
+ * Areas defined by currently visible apps that they prefer to keep clear from overlays such as
+ * the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
+ * The system will try to respect these areas, but when not possible will ignore them.
+ *
+ * @see android.view.View#setPreferKeepClearRects
+ */
+ private final Set<Rect> mRestrictedKeepClearAreas = new ArraySet<>();
+ /**
+ * Areas defined by currently visible apps holding
+ * {@link android.Manifest.permission#SET_UNRESTRICTED_KEEP_CLEAR_AREAS} that they prefer to
+ * keep clear from overlays such as the PiP.
+ * Unrestricted areas can move the PiP farther than restricted areas, and the system will try
+ * harder to respect these areas.
+ *
+ * @see android.view.View#setPreferKeepClearRects
+ */
+ private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -201,7 +228,8 @@ public final class PipBoundsState {
new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
);
} catch (RemoteException e) {
- Log.e(TAG, "Unable to set alert PiP state change.");
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to set alert PiP state change.", TAG);
}
}
@@ -365,14 +393,33 @@ public final class PipBoundsState {
}
}
+ /** Set the keep clear areas onscreen. The PiP should ideally not cover them. */
+ public void setKeepClearAreas(@NonNull Set<Rect> restrictedAreas,
+ @NonNull Set<Rect> unrestrictedAreas) {
+ mRestrictedKeepClearAreas.clear();
+ mRestrictedKeepClearAreas.addAll(restrictedAreas);
+ mUnrestrictedKeepClearAreas.clear();
+ mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
+ }
+
+ @NonNull
+ public Set<Rect> getRestrictedKeepClearAreas() {
+ return mRestrictedKeepClearAreas;
+ }
+
+ @NonNull
+ public Set<Rect> getUnrestrictedKeepClearAreas() {
+ return mUnrestrictedKeepClearAreas;
+ }
+
/**
* Initialize states when first entering PiP.
*/
- public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
- Size overrideMinSize) {
+ public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
setLastPipComponentName(componentName);
- setAspectRatio(aspectRatio);
- setOverrideMinSize(overrideMinSize);
+ setAspectRatio(pipBoundsAlgorithm.getAspectRatioOrDefault(params));
+ setOverrideMinSize(pipBoundsAlgorithm.getMinimalSize(activityInfo));
}
/** Returns whether the shelf is currently showing. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 97139626a3d2..8a50f2233573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -144,7 +144,7 @@ public class PipMediaController {
mediaControlFilter.addAction(ACTION_NEXT);
mediaControlFilter.addAction(ACTION_PREV);
mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
- SYSTEMUI_PERMISSION, mainHandler);
+ SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
// Creates the standard media buttons that we may show.
mPauseAction = getDefaultRemoteAction(R.string.pip_pause,
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 caa1f017082b..f6ff294b4328 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
@@ -66,7 +66,7 @@ public interface PipMenuController {
/**
* Given a set of actions, update the menu.
*/
- void setAppActions(ParceledListSlice<RemoteAction> appActions);
+ void setAppActions(ParceledListSlice<RemoteAction> appActions, RemoteAction closeAction);
/**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 180e3fb48c9d..d7322ce7beda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -138,8 +138,8 @@ public class PipSurfaceTransactionHelper {
// destination are different.
final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
final Rect crop = mTmpDestinationRect;
- crop.set(0, 0, Transitions.ENABLE_SHELL_TRANSITIONS ? destH
- : destW, Transitions.ENABLE_SHELL_TRANSITIONS ? destW : destH);
+ crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
+ : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
// Inverse scale for crop to fit in screen coordinates.
crop.scale(1 / scale);
crop.offset(insets.left, insets.top);
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 a201616db208..5d6b041f9006 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
@@ -18,13 +18,14 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
import static android.util.RotationUtils.rotateBounds;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -40,6 +41,10 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -57,7 +62,6 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.util.Log;
import android.util.Rational;
import android.view.Display;
import android.view.Surface;
@@ -67,6 +71,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -75,8 +80,8 @@ 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.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -127,7 +132,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final int mExitAnimationDuration;
private final int mCrossFadeAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -243,7 +247,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* An optional overlay used to mask content changing between an app in/out of PiP, only set if
* {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
*/
- private SurfaceControl mSwipePipToHomeOverlay;
+ @Nullable
+ SurfaceControl mSwipePipToHomeOverlay;
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@@ -254,7 +259,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
@NonNull PipTransitionController pipTransitionController,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@@ -277,7 +281,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
- mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -304,6 +307,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mPipTransitionState.isInPip();
}
+ private boolean isLaunchIntoPipTask() {
+ return mPictureInPictureParams != null && mPictureInPictureParams.isLaunchIntoPip();
+ }
+
/**
* Returns whether the entry animation is waiting to be started.
*/
@@ -346,12 +353,24 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Callback when launcher finishes swipe-pip-to-home operation.
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
- public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds,
+ public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
SurfaceControl overlay) {
// do nothing if there is no startSwipePipToHome being called before
- if (mPipTransitionState.getInSwipePipToHomeTransition()) {
- mPipBoundsState.setBounds(destinationBounds);
- mSwipePipToHomeOverlay = overlay;
+ if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
+ return;
+ }
+ mPipBoundsState.setBounds(destinationBounds);
+ mSwipePipToHomeOverlay = overlay;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // With Shell transition, the overlay was attached to the remote transition leash, which
+ // will be removed when the current transition is finished, so we need to reparent it
+ // to the actual Task surface now.
+ // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+ // transition.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, t);
+ t.setLayer(overlay, Integer.MAX_VALUE);
+ t.apply();
}
}
@@ -363,11 +382,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mLeash;
}
- private void setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params,
- ActivityInfo activityInfo) {
- mPipBoundsState.setBoundsStateForEntry(componentName,
- mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
- mPipBoundsAlgorithm.getMinimalSize(activityInfo));
+ private void setBoundsStateForEntry(ComponentName componentName,
+ PictureInPictureParams params, ActivityInfo activityInfo) {
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
+ mPipBoundsAlgorithm);
}
/**
@@ -386,35 +404,62 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!mPipTransitionState.isInPip()
|| mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
|| mToken == null) {
- Log.wtf(TAG, "Not allowed to exitPip in current state"
- + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
+ ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not allowed to exitPip in current state"
+ + " mState=%d mToken=%s", TAG, mPipTransitionState.getTransitionState(),
+ mToken);
return;
}
- mPipUiEventLoggerLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (isLaunchIntoPipTask()) {
+ exitLaunchIntoPipTask(wct);
+ return;
+ }
+
+ if (ENABLE_SHELL_TRANSITIONS) {
+ if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
+ mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
+ isPipTopLeft()
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mPipTransitionController.startExitTransition(
+ TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */);
+ return;
+ }
+ }
+
final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
: TRANSITION_DIRECTION_LEAVE_PIP;
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mPipBoundsState.getBounds());
- tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
- // We set to fullscreen here for now, but later it will be set to UNDEFINED for
- // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
- wct.setActivityWindowingMode(mToken,
- direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_FULLSCREEN);
- wct.setBounds(mToken, destinationBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+ // When exit to fullscreen with Shell transition enabled, we update the Task windowing
+ // mode directly so that it can also trigger display rotation and visibility update in
+ // the same transition if there will be any.
+ wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ // We can inherit the parent bounds as it is going to be fullscreen. The
+ // destinationBounds calculated above will be incorrect if this is with rotation.
+ wct.setBounds(mToken, null);
+ } else {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
+ mPipBoundsState.getBounds());
+ tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
+ // We set to fullscreen here for now, but later it will be set to UNDEFINED for
+ // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
+ wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
+ wct.setBounds(mToken, destinationBounds);
+ wct.setBoundsChangeTransaction(mToken, tx);
+ }
+
// Set the exiting state first so if there is fixed rotation later, the running animation
// won't be interrupted by alpha animation for existing PiP.
mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mPipTransitionController.startTransition(destinationBounds, wct);
+ mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
mSyncTransactionQueue.queue(wct);
@@ -438,25 +483,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
});
}
+ private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
+ wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
+ mTaskOrganizer.applyTransaction(wct);
+
+ // Remove the PiP with fade-out animation right after the host Task is brought to front.
+ removePip();
+ }
+
private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
// Reset the final windowing mode.
wct.setWindowingMode(mToken, getOutPipWindowingMode());
// Simply reset the activity mode set prior to the animation running.
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- mLegacySplitScreenOptional.ifPresent(splitScreen -> {
- if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
- wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
- }
- });
}
/**
* Removes PiP immediately.
*/
public void removePip() {
- if (!mPipTransitionState.isInPip() || mToken == null) {
- Log.wtf(TAG, "Not allowed to removePip in current state"
- + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
+ if (!mPipTransitionState.isInPip() || mToken == 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);
return;
}
@@ -479,7 +529,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
wct.setBounds(mToken, null);
wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.reorder(mToken, false);
- mPipTransitionController.startTransition(null, wct);
+ mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
+ null /* destinationBounds */);
return;
}
@@ -492,7 +543,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
ActivityTaskManager.getService().removeRootTasksInWindowingModes(
new int[]{ WINDOWING_MODE_PINNED });
} catch (RemoteException e) {
- Log.e(TAG, "Failed to remove PiP", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to remove PiP, %s",
+ TAG, e);
}
}
@@ -521,7 +574,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
} else {
- Log.d(TAG, "Defer onTaskAppeared-SwipePipToHome until end of fixed rotation.");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Defer onTaskAppeared-SwipePipToHome until end of fixed rotation.",
+ TAG);
}
return;
}
@@ -529,9 +584,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (mOneShotAnimationType == ANIM_TYPE_ALPHA
&& SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
> ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
- Log.d(TAG, "Alpha animation is expired. Use bounds animation.");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Alpha animation is expired. Use bounds animation.", TAG);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
}
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // For Shell transition, we will animate the window in PipTransition#startAnimation
+ // instead of #onTaskAppeared.
+ return;
+ }
+
if (mWaitForFixedRotation) {
onTaskAppearedWithFixedRotation();
return;
@@ -541,15 +604,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- mPipMenuController.attach(mLeash);
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- }
- return;
- }
-
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
mPipMenuController.attach(mLeash);
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
@@ -568,8 +622,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private void onTaskAppearedWithFixedRotation() {
if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- Log.d(TAG, "Defer entering PiP alpha animation, fixed rotation is ongoing");
- // If deferred, hide the surface till fixed rotation is completed.
+ 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.
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
tx.setAlpha(mLeash, 0f);
@@ -626,7 +681,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private void onEndOfSwipePipToHomeTransition() {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mSwipePipToHomeOverlay = null;
return;
}
@@ -698,7 +752,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
- * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
+ * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int, boolean)}.
* Meanwhile this callback is invoked whenever the task is removed. For instance:
* - as a result of removeRootTasksInWindowingModes from WM
* - activity itself is died
@@ -710,24 +764,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
return;
}
+ if (Transitions.ENABLE_SHELL_TRANSITIONS
+ && mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP) {
+ // With Shell transition, we do the cleanup in PipTransition after exiting PIP.
+ return;
+ }
final WindowContainerToken token = info.token;
Objects.requireNonNull(token, "Requires valid WindowContainerToken");
if (token.asBinder() != mToken.asBinder()) {
- Log.wtf(TAG, "Unrecognized token: " + token);
+ ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unrecognized token: %s", TAG, token);
return;
}
- clearWaitForFixedRotation();
- mPipTransitionState.setInSwipePipToHomeTransition(false);
- mPictureInPictureParams = null;
- mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
- // Re-set the PIP bounds to none.
- mPipBoundsState.setBounds(new Rect());
- mPipUiEventLoggerLogger.setTaskInfo(null);
- mPipMenuController.detach();
-
- if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
- mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
- }
+ onExitPipFinished(info);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mPipTransitionController.forceFinishTransition();
@@ -749,8 +798,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
if (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP
&& mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP) {
- Log.d(TAG, "Defer onTaskInfoChange in current state: "
- + mPipTransitionState.getTransitionState());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Defer onTaskInfoChange in current state: %d", TAG,
+ mPipTransitionState.getTransitionState());
// Defer applying PiP parameters if the task is entering PiP to avoid disturbing
// the animation.
mDeferredTaskInfo = info;
@@ -761,7 +811,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
final PictureInPictureParams newParams = info.pictureInPictureParams;
if (newParams == null || !applyPictureInPictureParams(newParams)) {
- Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Ignored onTaskInfoChanged with PiP param: %s", TAG, newParams);
return;
}
// Aspect ratio changed, re-calculate bounds if valid.
@@ -784,10 +835,38 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
+ if (mTaskInfo == null || mLeash == null || mTaskInfo.taskId != taskId) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ return mLeash;
+ }
+
+ @Override
public void onFixedRotationStarted(int displayId, int newRotation) {
mNextRotation = newRotation;
mWaitForFixedRotation = true;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // The fixed rotation will also be included in the transition info. However, if it is
+ // not a PIP transition (such as open another app to different orientation),
+ // PIP transition handler may not be aware of the fixed rotation start.
+ // Notify the PIP transition handler so that it can fade out the PIP window early for
+ // fixed transition of other windows.
+ mPipTransitionController.onFixedRotationStarted();
+ return;
+ }
+
if (mPipTransitionState.isInPip()) {
// Fade out the existing PiP to avoid jump cut during seamless rotation.
fadeExistingPip(false /* show */);
@@ -799,6 +878,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!mWaitForFixedRotation) {
return;
}
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ clearWaitForFixedRotation();
+ return;
+ }
if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
onEndOfSwipePipToHomeTransition();
@@ -824,9 +907,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
clearWaitForFixedRotation();
}
+ /** Called when exiting PIP transition is finished to do the state cleanup. */
+ void onExitPipFinished(TaskInfo info) {
+ clearWaitForFixedRotation();
+ if (mSwipePipToHomeOverlay != null) {
+ removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
+ mSwipePipToHomeOverlay = null;
+ }
+ mPipTransitionState.setInSwipePipToHomeTransition(false);
+ mPictureInPictureParams = null;
+ mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
+ // Re-set the PIP bounds to none.
+ mPipBoundsState.setBounds(new Rect());
+ mPipUiEventLoggerLogger.setTaskInfo(null);
+ mPipMenuController.detach();
+
+ if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
+ mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
+ }
+ }
+
private void fadeExistingPip(boolean show) {
if (mLeash == null || !mLeash.isValid()) {
- Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash);
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid leash on fadeExistingPip: %s", TAG, mLeash);
return;
}
final float alphaStart = show ? 0 : 1;
@@ -873,11 +977,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if ((mPipTransitionState.getInSwipePipToHomeTransition()
|| waitForFixedRotationOnEnteringPip) && fromRotation) {
if (DEBUG) {
- Log.d(TAG, "Skip onMovementBoundsChanged on rotation change"
- + " InSwipePipToHomeTransition="
- + mPipTransitionState.getInSwipePipToHomeTransition()
- + " mWaitForFixedRotation=" + mWaitForFixedRotation
- + " getTransitionState=" + mPipTransitionState.getTransitionState());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Skip onMovementBoundsChanged on rotation change"
+ + " InSwipePipToHomeTransition=%b"
+ + " mWaitForFixedRotation=%b"
+ + " getTransitionState=%d", TAG,
+ mPipTransitionState.getInSwipePipToHomeTransition(), mWaitForFixedRotation,
+ mPipTransitionState.getTransitionState());
}
return;
}
@@ -886,7 +992,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
final boolean rotatingPip = mPipTransitionState.isInPip() && fromRotation;
- if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
+ if (rotatingPip && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // The animation and surface update will be handled by the shell transition handler.
+ mPipBoundsState.setBounds(destinationBoundsOut);
+ } else if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
// The position will be used by fade-in animation when the fixed rotation is done.
mPipBoundsState.setBounds(destinationBoundsOut);
} else if (rotatingPip) {
@@ -962,13 +1071,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
final Rational currentAspectRatio =
- mPictureInPictureParams != null ? mPictureInPictureParams.getAspectRatioRational()
+ mPictureInPictureParams != null ? mPictureInPictureParams.getAspectRatio()
: null;
final boolean aspectRatioChanged = !Objects.equals(currentAspectRatio,
- params.getAspectRatioRational());
+ params.getAspectRatio());
mPictureInPictureParams = params;
if (aspectRatioChanged) {
- mPipBoundsState.setAspectRatio(params.getAspectRatio());
+ mPipBoundsState.setAspectRatio(params.getAspectRatioFloat());
}
return aspectRatioChanged;
}
@@ -989,7 +1098,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@PipAnimationController.TransitionDirection int direction,
Consumer<Rect> updateBoundsCallback) {
if (mWaitForFixedRotation) {
- Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: skip scheduleAnimateResizePip, entering pip deferred", TAG);
return;
}
scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, 0 /* startingAngle */,
@@ -1003,7 +1113,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
float startingAngle, Consumer<Rect> updateBoundsCallback) {
if (mWaitForFixedRotation) {
- Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: skip scheduleAnimateResizePip, entering pip deferred", TAG);
return;
}
scheduleAnimateResizePip(fromBounds, toBounds, startingAngle, null /* sourceHintRect */,
@@ -1041,7 +1152,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
// Could happen when exitPip
if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Abort animation, invalid leash", TAG);
return;
}
mPipBoundsState.setBounds(toBounds);
@@ -1076,12 +1188,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Consumer<Rect> updateBoundsCallback) {
// Could happen when exitPip
if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Abort animation, invalid leash", TAG);
return;
}
if (startBounds.isEmpty() || toBounds.isEmpty()) {
- Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Attempted to user resize PIP to or from empty bounds, aborting.", TAG);
return;
}
@@ -1156,7 +1270,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
if (mWaitForFixedRotation) {
- Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: skip scheduleOffsetPip, entering pip deferred", TAG);
return;
}
offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
@@ -1169,7 +1284,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
if (mTaskInfo == null) {
- Log.w(TAG, "mTaskInfo is not set");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: mTaskInfo is not set",
+ TAG);
return;
}
final Rect destinationBounds = new Rect(originalBounds);
@@ -1281,7 +1397,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
@PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
- mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct);
+ mSplitScreenOptional.ifPresent(splitScreenController ->
+ splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
} else {
mTaskOrganizer.applyTransaction(wct);
}
@@ -1313,7 +1430,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
float startingAngle) {
// Could happen when exitPip
if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Abort animation, invalid leash", TAG);
return null;
}
final int rotationDelta = mWaitForFixedRotation
@@ -1380,43 +1498,28 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
- * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination
- * bounds if PiP is going to split screen.
+ * Sync with {@link SplitScreenController} on destination bounds if PiP is going to
+ * split screen.
*
* @param destinationBoundsOut contain the updated destination bounds if applicable
* @return {@code true} if destinationBounds is altered for split screen
*/
private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
- if (enterSplit && mSplitScreenOptional.isPresent()) {
- final Rect topLeft = new Rect();
- final Rect bottomRight = new Rect();
- mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
- final boolean isPipTopLeft = isPipTopLeft();
- destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
- return true;
- }
-
- if (!mLegacySplitScreenOptional.isPresent()) {
- return false;
- }
-
- LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get();
- if (!legacySplitScreen.isDividerVisible()) {
- // fail early if system is not in split screen mode
+ if (!enterSplit || !mSplitScreenOptional.isPresent()) {
return false;
}
-
- // PiP window will go to split-secondary mode instead of fullscreen, populates the
- // split screen bounds here.
- destinationBoundsOut.set(legacySplitScreen.getDividerView()
- .getNonMinimizedSplitScreenSecondaryBounds());
+ final Rect topLeft = new Rect();
+ final Rect bottomRight = new Rect();
+ mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
+ final boolean isPipTopLeft = isPipTopLeft();
+ destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
return true;
}
/**
* Fades out and removes an overlay surface.
*/
- private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
+ void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
boolean withStartDelay) {
if (surface == null) {
return;
@@ -1428,7 +1531,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Could happen if onTaskVanished happens during the animation since we may have
// set a start delay on this animation.
- Log.d(TAG, "Task vanished, skip fadeOutAndRemoveOverlay");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Task vanished, skip fadeOutAndRemoveOverlay", TAG);
animation.removeAllListeners();
animation.removeAllUpdateListeners();
animation.cancel();
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 b31e6e0750ce..48df28ee4cde 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
@@ -16,42 +16,60 @@
package com.android.wm.shell.pip;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
+import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
-import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
+
/**
* Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
* exit animation.
@@ -60,12 +78,32 @@ public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ private final Context mContext;
private final PipTransitionState mPipTransitionState;
private final int mEnterExitAnimationDuration;
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final Optional<SplitScreenController> mSplitScreenOptional;
private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
- private Rect mExitDestinationBounds = new Rect();
- private IBinder mExitTransition = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+ private final Rect mExitDestinationBounds = new Rect();
+ @Nullable
+ private IBinder mExitTransition;
+ private IBinder mRequestedEnterTransition;
+ private WindowContainerToken mRequestedEnterTask;
+ /** The Task window that is currently in PIP windowing mode. */
+ @Nullable
+ private WindowContainerToken mCurrentPipTaskToken;
+ /** Whether display is in fixed rotation. */
+ private boolean mInFixedRotation;
+ /**
+ * The rotation that the display will apply after expanding PiP to fullscreen. This is only
+ * meaningful if {@link #mInFixedRotation} is true.
+ */
+ @Surface.Rotation
+ private int mEndFixedRotation;
+ /** Whether the PIP window has fade out for fixed rotation. */
+ private boolean mHasFadeOut;
public PipTransition(Context context,
PipBoundsState pipBoundsState,
@@ -74,12 +112,17 @@ public class PipTransition extends PipTransitionController {
PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController,
Transitions transitions,
- @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreenController> splitScreenOptional) {
super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
pipAnimationController, transitions, shellTaskOrganizer);
+ mContext = context;
mPipTransitionState = pipTransitionState;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
+ mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ mSplitScreenOptional = splitScreenOptional;
}
@Override
@@ -97,97 +140,105 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @Nullable Rect destinationBounds) {
if (destinationBounds != null) {
mExitDestinationBounds.set(destinationBounds);
- mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
- } else {
- mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this);
}
+ mExitTransition = mTransitions.startTransition(type, out, this);
}
@Override
- public boolean startAnimation(@android.annotation.NonNull IBinder transition,
- @android.annotation.NonNull TransitionInfo info,
- @android.annotation.NonNull SurfaceControl.Transaction startTransaction,
- @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
- @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
-
- if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) {
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ mInFixedRotation = fixedRotationChange != null;
+ mEndFixedRotation = mInFixedRotation
+ ? fixedRotationChange.getEndFixedRotation()
+ : ROTATION_UNDEFINED;
+
+ // Exiting PIP.
+ final int type = info.getType();
+ if (transition.equals(mExitTransition)) {
+ mExitDestinationBounds.setEmpty();
mExitTransition = null;
- if (info.getChanges().size() == 1) {
- if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(null, null);
- mFinishCallback = null;
- throw new RuntimeException("Previous callback not called, aborting exit PIP.");
- }
-
- final TransitionInfo.Change change = info.getChanges().get(0);
- mFinishCallback = finishCallback;
- startTransaction.apply();
- boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(),
- new Rect(mExitDestinationBounds));
- mExitDestinationBounds.setEmpty();
- return success;
- } else {
- Log.e(TAG, "Got an exit-pip transition with unexpected change-list");
+ mHasFadeOut = false;
+ if (mFinishCallback != null) {
+ callFinishCallback(null /* wct */);
+ mFinishTransaction = null;
+ throw new RuntimeException("Previous callback not called, aborting exit PIP.");
}
- }
- if (info.getType() == TRANSIT_REMOVE_PIP) {
- if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
- mFinishCallback = null;
- throw new RuntimeException("Previous callback not called, aborting remove PIP.");
+ // PipTaskChange can be null if the PIP task has been detached, for example, when the
+ // task contains multiple activities, the PIP will be moved to a new PIP task when
+ // entering, and be moved back when exiting. In that case, the PIP task will be removed
+ // immediately.
+ final TaskInfo pipTaskInfo = currentPipTaskChange != null
+ ? currentPipTaskChange.getTaskInfo()
+ : mPipOrganizer.getTaskInfo();
+ if (pipTaskInfo == null) {
+ throw new RuntimeException("Cannot find the pip task for exit-pip transition.");
}
- startTransaction.apply();
- finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
- mPipBoundsState.getDisplayBounds());
- finishCallback.onTransitionFinished(null, null);
+ switch (type) {
+ case TRANSIT_EXIT_PIP:
+ startExitAnimation(info, startTransaction, finishTransaction, finishCallback,
+ pipTaskInfo, currentPipTaskChange);
+ break;
+ case TRANSIT_EXIT_PIP_TO_SPLIT:
+ startExitToSplitAnimation(info, startTransaction, finishTransaction,
+ finishCallback, pipTaskInfo);
+ break;
+ case TRANSIT_REMOVE_PIP:
+ removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
+ pipTaskInfo);
+ break;
+ default:
+ throw new IllegalStateException("mExitTransition with unexpected transit type="
+ + transitTypeToString(type));
+ }
+ mCurrentPipTaskToken = null;
return true;
+ } else if (transition == mRequestedEnterTransition) {
+ mRequestedEnterTransition = null;
+ mRequestedEnterTask = null;
}
- // We only support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
- // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
- if (info.getType() != TRANSIT_PIP && info.getType() != TRANSIT_OPEN) {
- return false;
+ // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can
+ // happen when a new activity requests enter PIP). In this case, we just show this Task in
+ // its end state, and play other animation as normal.
+ if (currentPipTaskChange != null
+ && currentPipTaskChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) {
+ resetPrevPip(currentPipTaskChange, startTransaction);
}
- // Search for an Enter PiP transition (along with a show wallpaper one)
- TransitionInfo.Change enterPip = null;
- TransitionInfo.Change wallpaper = null;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null
- && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_PINNED) {
- enterPip = change;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
- }
- }
- if (enterPip == null) {
- return false;
+ // Entering PIP.
+ if (isEnteringPip(info, mCurrentPipTaskToken)) {
+ return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
}
- if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
- mFinishCallback = null;
- throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ // For transition that we don't animate, but contains the PIP leash, we need to update the
+ // PIP surface, otherwise it will be reset after the transition.
+ if (currentPipTaskChange != null) {
+ updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
+ finishTransaction);
}
- // Show the wallpaper if there is a wallpaper change.
- if (wallpaper != null) {
- startTransaction.show(wallpaper.getLeash());
- startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
+ // Fade in the fadeout PIP when the fixed rotation is finished.
+ if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) {
+ fadeExistingPip(true /* show */);
}
- mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
- mFinishCallback = finishCallback;
- return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
- startTransaction, finishTransaction, enterPip.getStartRotation(),
- enterPip.getEndRotation());
+ return false;
+ }
+
+ /** Helper to identify whether this handler is currently the one playing an animation */
+ private boolean isAnimatingLocally() {
+ return mFinishTransaction != null;
}
@Nullable
@@ -196,8 +247,9 @@ public class PipTransition extends PipTransitionController {
@NonNull TransitionRequestInfo request) {
if (request.getType() == TRANSIT_PIP) {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mRequestedEnterTransition = transition;
+ mRequestedEnterTask = request.getTriggerTask().token;
wct.setActivityWindowingMode(request.getTriggerTask().token,
WINDOWING_MODE_UNDEFINED);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -210,6 +262,23 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ public boolean handleRotateDisplay(int startRotation, int endRotation,
+ WindowContainerTransaction wct) {
+ if (mRequestedEnterTransition != null && mOneShotAnimationType == 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) {
+ mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ wct.setBounds(mRequestedEnterTask, destinationBounds);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public void onTransitionMerged(@NonNull IBinder transition) {
if (transition != mExitTransition) {
return;
@@ -227,54 +296,349 @@ public class PipTransition extends PipTransitionController {
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
- new Rect(mExitDestinationBounds));
+ new Rect(mExitDestinationBounds), Surface.ROTATION_0);
}
mExitDestinationBounds.setEmpty();
+ mCurrentPipTaskToken = null;
}
@Override
public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
@Nullable SurfaceControl.Transaction tx) {
-
- if (isInPipDirection(direction)) {
- mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
+ final boolean enteringPip = isInPipDirection(direction);
+ if (enteringPip) {
+ mPipTransitionState.setTransitionState(ENTERED_PIP);
}
- // If there is an expected exit transition, then the exit will be "merged" into this
- // transition so don't fire the finish-callback in that case.
- if (mExitTransition == null && mFinishCallback != null) {
+ // If we have an exit transition, but aren't playing a transition locally, it
+ // means we're expecting the exit transition will be "merged" into another transition
+ // (likely a remote like launcher), so don't fire the finish-callback here -- wait until
+ // the exit transition is merged.
+ if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
WindowContainerTransaction wct = new WindowContainerTransaction();
prepareFinishResizeTransaction(taskInfo, destinationBounds,
direction, wct);
if (tx != null) {
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
- mFinishCallback.onTransitionFinished(wct, null /* callback */);
- mFinishCallback = null;
+ final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
+ final int displayRotation = taskInfo.getConfiguration().windowConfiguration
+ .getDisplayRotation();
+ if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
+ && leash != null && leash.isValid()) {
+ // 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 finishBounds = new Rect(destinationBounds);
+ rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
+ mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+ }
+ mFinishTransaction = null;
+ callFinishCallback(wct);
}
finishResizeForMenu(destinationBounds);
}
+ private void callFinishCallback(WindowContainerTransaction wct) {
+ // Need to unset mFinishCallback first because onTransitionFinished can re-enter this
+ // handler if there is a pending PiP animation.
+ final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
+ mFinishCallback = null;
+ finishCallback.onTransitionFinished(wct, null /* callback */);
+ }
+
@Override
public void forceFinishTransition() {
if (mFinishCallback == null) return;
mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
mFinishCallback = null;
+ mFinishTransaction = null;
+ }
+
+ @Override
+ public void onFixedRotationStarted() {
+ // 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) {
+ // Fade out the existing PiP to avoid jump cut during seamless rotation.
+ fadeExistingPip(false /* show */);
+ }
+ }
+
+ @Nullable
+ private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) {
+ if (mCurrentPipTaskToken == null) {
+ return null;
+ }
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mCurrentPipTaskToken.equals(change.getContainer())) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ private void startExitAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
+ TransitionInfo.Change pipChange = pipTaskChange;
+ if (pipChange == null) {
+ // The pipTaskChange is null, this can happen if we are reparenting the PIP activity
+ // back to its original Task. In that case, we should animate the activity leash
+ // instead, which should be the only non-task, independent, TRANSIT_CHANGE window.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null && change.getMode() == TRANSIT_CHANGE
+ && TransitionInfo.isIndependent(change, info)) {
+ pipChange = change;
+ break;
+ }
+ }
+ }
+ if (pipChange == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: No window of exiting PIP is found. Can't play expand animation", TAG);
+ removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
+ taskInfo);
+ return;
+ }
+ mFinishCallback = (wct, wctCB) -> {
+ mPipOrganizer.onExitPipFinished(taskInfo);
+ finishCallback.onTransitionFinished(wct, wctCB);
+ };
+ mFinishTransaction = finishTransaction;
+
+ // Check if it is Shell rotation.
+ if (Transitions.SHELL_TRANSITIONS_ROTATION) {
+ TransitionInfo.Change displayRotationChange = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getMode() == TRANSIT_CHANGE
+ && (change.getFlags() & FLAG_IS_DISPLAY) != 0
+ && change.getStartRotation() != change.getEndRotation()) {
+ displayRotationChange = change;
+ break;
+ }
+ }
+ if (displayRotationChange != null) {
+ // Exiting PIP to fullscreen with orientation change.
+ startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
+ displayRotationChange, taskInfo, pipChange);
+ return;
+ }
+ }
+
+ // Set the initial frame as scaling the end to the start.
+ final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
+ final Point offset = pipChange.getEndRelOffset();
+ destinationBounds.offset(-offset.x, -offset.y);
+ startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds);
+ mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(),
+ destinationBounds, mPipBoundsState.getBounds());
+ startTransaction.apply();
+
+ // Check if it is fixed rotation.
+ final int rotationDelta;
+ if (mInFixedRotation) {
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = mEndFixedRotation;
+ rotationDelta = deltaRotation(startRotation, endRotation);
+ final Rect endBounds = new Rect(destinationBounds);
+
+ // Set the end frame since the display won't rotate until fixed rotation is finished
+ // in the next display change transition.
+ rotateBounds(endBounds, destinationBounds, rotationDelta);
+ final int degree, x, y;
+ if (rotationDelta == ROTATION_90) {
+ degree = 90;
+ x = destinationBounds.right;
+ y = destinationBounds.top;
+ } else {
+ degree = -90;
+ x = destinationBounds.left;
+ y = destinationBounds.bottom;
+ }
+ mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
+ pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y,
+ true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
+ } else {
+ rotationDelta = Surface.ROTATION_0;
+ }
+ startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta);
+ }
+
+ private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull TransitionInfo.Change displayRotationChange,
+ @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) {
+ final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
+ displayRotationChange.getEndRotation());
+
+ // Counter-rotate all "going-away" things since they are still in the old orientation.
+ final CounterRotatorHelper rotator = new CounterRotatorHelper();
+ rotator.handleClosingChanges(info, startTransaction, displayRotationChange);
+
+ // Get the start bounds in new orientation.
+ final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
+ rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
+ final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
+ final Point offset = pipChange.getEndRelOffset();
+ startBounds.offset(-offset.x, -offset.y);
+ endBounds.offset(-offset.x, -offset.y);
+
+ // Reverse the rotation direction for expansion.
+ final int pipRotateDelta = deltaRotation(rotateDelta, 0);
+
+ // Set the start frame.
+ final int degree, x, y;
+ if (pipRotateDelta == ROTATION_90) {
+ degree = 90;
+ x = startBounds.right;
+ y = startBounds.top;
+ } else {
+ degree = -90;
+ x = startBounds.left;
+ y = startBounds.bottom;
+ }
+ mSurfaceTransactionHelper.rotateAndScaleWithCrop(startTransaction, pipChange.getLeash(),
+ endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */,
+ pipRotateDelta == ROTATION_270 /* clockwise */);
+ startTransaction.apply();
+ rotator.cleanUp(finishTransaction);
+
+ // Expand and rotate the pip window to fullscreen.
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
+ startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
+ 0 /* startingAngle */, pipRotateDelta);
+ animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start();
}
- private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final Rect destinationBounds) {
- PipAnimationController.PipTransitionAnimator animator =
+ private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
+ final Rect destinationBounds, final int rotationDelta) {
+ final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
mPipBoundsState.getBounds(), destinationBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
-
+ TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
.start();
+ }
- return true;
+ /** For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. */
+ private void removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull TaskInfo taskInfo) {
+ startTransaction.apply();
+ finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+ mPipBoundsState.getDisplayBounds());
+ mPipOrganizer.onExitPipFinished(taskInfo);
+ finishCallback.onTransitionFinished(null, null);
+ }
+
+ /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
+ private static boolean isEnteringPip(@NonNull TransitionInfo info,
+ @Nullable WindowContainerToken currentPipTaskToken) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+ && !change.getContainer().equals(currentPipTaskToken)) {
+ // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
+ // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
+ if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) {
+ return true;
+ }
+ // This can happen if the request to enter PIP happens when we are collecting for
+ // another transition, such as TRANSIT_CHANGE (display rotation).
+ if (info.getType() == TRANSIT_CHANGE) {
+ return true;
+ }
+
+ // Please file a bug to handle the unexpected transition type.
+ throw new IllegalStateException("Entering PIP with unexpected transition type="
+ + transitTypeToString(info.getType()));
+ }
+ }
+ return false;
+ }
+
+ private boolean startEnterAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Search for an Enter PiP transition (along with a show wallpaper one)
+ TransitionInfo.Change enterPip = null;
+ TransitionInfo.Change wallpaper = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ enterPip = change;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
+ }
+ }
+ if (enterPip == null) {
+ return false;
+ }
+ // Keep track of the PIP task.
+ mCurrentPipTaskToken = enterPip.getContainer();
+ mHasFadeOut = false;
+
+ if (mFinishCallback != null) {
+ callFinishCallback(null /* wct */);
+ mFinishTransaction = null;
+ throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ }
+
+ // Show the wallpaper if there is a wallpaper change.
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash());
+ startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
+ }
+ // Make sure other open changes are visible as entering PIP. Some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == enterPip || change == wallpaper) {
+ continue;
+ }
+ if (isOpeningType(change.getMode())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.show(leash).setAlpha(leash, 1.f);
+ }
+ }
+
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
+ mFinishCallback = finishCallback;
+ mFinishTransaction = finishTransaction;
+ final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation();
+ return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
+ startTransaction, finishTransaction, enterPip.getStartRotation(),
+ endRotation);
}
private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
@@ -285,48 +649,70 @@ public class PipTransition extends PipTransitionController {
taskInfo.topActivityInfo);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ int rotationDelta = deltaRotation(startRotation, endRotation);
+ Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+ taskInfo.pictureInPictureParams, currentBounds);
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // Need to get the bounds of new rotation in old rotation for fixed rotation,
+ computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
+ destinationBounds, sourceHintRect);
+ }
PipAnimationController.PipTransitionAnimator animator;
- finishTransaction.setPosition(leash, destinationBounds.left, destinationBounds.top);
+ // Set corner radius for entering pip.
+ mSurfaceTransactionHelper
+ .crop(finishTransaction, leash, destinationBounds)
+ .round(finishTransaction, leash, true /* applyCornerRadius */);
+ mPipMenuController.attach(leash);
+
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-
- // 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(leash);
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
+ final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
+ startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
.setPosition(leash, destinationBounds.left, destinationBounds.top)
.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
- startTransaction.merge(tx);
+ if (swipePipToHomeOverlay != null) {
+ // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
+ // reparent the PIP activity to a new PIP task (in case there are other activities
+ // in the original Task), so we should also reparent the overlay to the PIP task.
+ startTransaction.reparent(swipePipToHomeOverlay, leash)
+ .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
+ mPipOrganizer.mSwipePipToHomeOverlay = null;
+ }
startTransaction.apply();
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // For fixed rotation, set the destination bounds to the new rotation coordinates
+ // at the end.
+ destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ }
mPipBoundsState.setBounds(destinationBounds);
onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+ if (swipePipToHomeOverlay != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
+ null /* callback */, false /* withStartDelay */);
+ }
mPipTransitionState.setInSwipePipToHomeTransition(false);
return true;
}
- int rotationDelta = deltaRotation(endRotation, startRotation);
if (rotationDelta != Surface.ROTATION_0) {
Matrix tmpTransform = new Matrix();
- tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90
- ? Surface.ROTATION_270 : Surface.ROTATION_90);
+ tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- final Rect sourceHintRect =
- PipBoundsAlgorithm.getValidSourceHintRect(
- taskInfo.pictureInPictureParams, currentBounds);
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.
+ animator.setUseContentOverlay(mContext);
+ }
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
startTransaction.setAlpha(leash, 0f);
- // 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(leash);
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -337,12 +723,131 @@ public class PipTransition extends PipTransitionController {
startTransaction.apply();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(mEnterExitAnimationDuration)
- .start();
+ .setDuration(mEnterExitAnimationDuration);
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // For fixed rotation, the animation destination bounds is in old rotation coordinates.
+ // Set the destination bounds to new coordinates after the animation is finished.
+ // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
+ animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ }
+ animator.start();
return true;
}
+ /** 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) {
+ mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ // Transform the destination bounds to current display coordinates.
+ rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
+ // When entering PiP (from button navigation mode), adjust the source rect hint by
+ // display cutout if applicable.
+ if (outSourceHintRect != null && taskInfo.displayCutoutInsets != null) {
+ if (rotationDelta == Surface.ROTATION_270) {
+ outSourceHintRect.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
+ }
+ }
+
+ private void startExitToSplitAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull TaskInfo taskInfo) {
+ final int changeSize = info.getChanges().size();
+ if (changeSize < 4) {
+ throw new RuntimeException(
+ "Got an exit-pip-to-split transition with unexpected change-list");
+ }
+ for (int i = changeSize - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final int mode = change.getMode();
+
+ if (mode == TRANSIT_CHANGE && change.getParent() != null) {
+ // TODO: perform resize/expand animation for reparented child task.
+ continue;
+ }
+
+ if (isOpeningType(mode) && change.getParent() == null) {
+ final SurfaceControl leash = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction
+ .show(leash)
+ .setAlpha(leash, 1f)
+ .setPosition(leash, endBounds.left, endBounds.top)
+ .setWindowCrop(leash, endBounds.width(), endBounds.height());
+ }
+ }
+ mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+ startTransaction.apply();
+
+ mPipOrganizer.onExitPipFinished(taskInfo);
+ finishCallback.onTransitionFinished(null, null);
+ }
+
+ private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
+ @NonNull SurfaceControl.Transaction startTransaction) {
+ final SurfaceControl leash = prevPipTaskChange.getLeash();
+ final Rect bounds = prevPipTaskChange.getEndAbsBounds();
+ final Point offset = prevPipTaskChange.getEndRelOffset();
+ bounds.offset(-offset.x, -offset.y);
+
+ startTransaction.setWindowCrop(leash, null);
+ startTransaction.setMatrix(leash, 1, 0, 0, 1);
+ startTransaction.setCornerRadius(leash, 0);
+ startTransaction.setPosition(leash, bounds.left, bounds.top);
+
+ if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ }
+ startTransaction.setAlpha(leash, 1);
+ }
+ mHasFadeOut = false;
+ mCurrentPipTaskToken = null;
+ mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo());
+ }
+
+ private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ // When the PIP window is visible and being a part of the transition, such as display
+ // rotation, we need to update its bounds and rounded corner.
+ final SurfaceControl leash = pipChange.getLeash();
+ final Rect destBounds = mPipBoundsState.getBounds();
+ final boolean isInPip = mPipTransitionState.isInPip();
+ mSurfaceTransactionHelper
+ .crop(startTransaction, leash, destBounds)
+ .round(startTransaction, leash, isInPip);
+ mSurfaceTransactionHelper
+ .crop(finishTransaction, leash, destBounds)
+ .round(finishTransaction, leash, isInPip);
+ }
+
+ /** Hides and shows the existing PIP during fixed rotation transition of other activities. */
+ private void fadeExistingPip(boolean show) {
+ final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
+ final TaskInfo taskInfo = mPipOrganizer.getTaskInfo();
+ if (leash == null || !leash.isValid() || taskInfo == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid leash on fadeExistingPip: %s", TAG, leash);
+ return;
+ }
+ final float alphaStart = show ? 0 : 1;
+ final float alphaEnd = show ? 1 : 0;
+ mPipAnimationController
+ .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
+ .setTransitionDirection(TRANSITION_DIRECTION_SAME)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start();
+ mHasFadeOut = !show;
+ }
+
private void finishResizeForMenu(Rect destinationBounds) {
mPipMenuController.movePipMenu(null, null, destinationBounds);
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 376f3298a83c..24993c621e3c 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
@@ -19,7 +19,9 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
+import android.annotation.Nullable;
import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.content.ComponentName;
@@ -68,6 +70,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
return;
}
+ if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlay(),
+ animator::clearContentOverlay, true /* withStartDelay*/);
+ }
onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
sendOnPipTransitionFinished(direction);
}
@@ -75,6 +81,11 @@ public abstract class PipTransitionController implements Transitions.TransitionH
@Override
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlay(),
+ animator::clearContentOverlay, true /* withStartDelay */);
+ }
sendOnPipTransitionCancelled(animator.getTransitionDirection());
}
};
@@ -98,9 +109,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
- * Called when the Shell wants to starts a transition/animation.
+ * Called when the Shell wants to start an exit Pip transition/animation.
*/
- public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @Nullable Rect destinationBounds) {
// Default implementation does nothing.
}
@@ -111,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
public void forceFinishTransition() {
}
+ /** Called when the fixed rotation started. */
+ public void onFixedRotationStarted() {
+ }
+
public PipTransitionController(PipBoundsState pipBoundsState,
PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController, Transitions transitions,
@@ -175,9 +191,19 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void setBoundsStateForEntry(ComponentName componentName,
PictureInPictureParams params,
ActivityInfo activityInfo) {
- mPipBoundsState.setBoundsStateForEntry(componentName,
- mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
- mPipBoundsAlgorithm.getMinimalSize(activityInfo));
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
+ mPipBoundsAlgorithm);
+ }
+
+ /**
+ * Called when the display is going to rotate.
+ *
+ * @return {@code true} if it was handled, otherwise the existing pip logic
+ * will deal with rotation.
+ */
+ public boolean handleRotateDisplay(int startRotation, int endRotation,
+ WindowContainerTransaction wct) {
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index a0a76d801cf4..9c23a32a7d2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -107,7 +107,10 @@ public class PipUiEventLogger {
PICTURE_IN_PICTURE_STASH_LEFT(710),
@UiEvent(doc = "User stashed picture-in-picture to the right side")
- PICTURE_IN_PICTURE_STASH_RIGHT(711);
+ PICTURE_IN_PICTURE_STASH_RIGHT(711),
+
+ @UiEvent(doc = "User taps on the settings button in PiP menu")
+ PICTURE_IN_PICTURE_SHOW_SETTINGS(933);
private final int mId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index da6d9804b29d..d7b69adf1241 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -24,9 +24,11 @@ import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Pair;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
/** A class that includes convenience methods. */
public class PipUtils {
private static final String TAG = "PipUtils";
@@ -51,7 +53,8 @@ public class PipUtils {
}
}
} catch (RemoteException e) {
- Log.w(TAG, "Unable to get pinned stack.");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to get pinned stack.", TAG);
}
return new Pair<>(null, 0);
}
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 101a55d8d367..5e4c12ed37ed 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
@@ -28,9 +28,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Debug;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -38,12 +36,15 @@ import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
@@ -118,13 +119,13 @@ public class PhonePipMenuController implements PipMenuController {
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
private final Optional<SplitScreenController> mSplitScreenController;
+ private final PipUiEventLogger mPipUiEventLogger;
private ParceledListSlice<RemoteAction> mAppActions;
private ParceledListSlice<RemoteAction> mMediaActions;
private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
private PipMenuView mPipMenuView;
- private IBinder mPipMenuInputToken;
private ActionListener mMediaActionListener = new ActionListener() {
@Override
@@ -150,6 +151,7 @@ public class PhonePipMenuController implements PipMenuController {
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
Optional<SplitScreenController> splitScreenOptional,
+ PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
@@ -158,6 +160,7 @@ public class PhonePipMenuController implements PipMenuController {
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mSplitScreenController = splitScreenOptional;
+ mPipUiEventLogger = pipUiEventLogger;
}
public boolean isMenuVisible() {
@@ -187,7 +190,7 @@ public class PhonePipMenuController implements PipMenuController {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
- mSplitScreenController);
+ mSplitScreenController, mPipUiEventLogger);
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
@@ -202,7 +205,6 @@ public class PhonePipMenuController implements PipMenuController {
mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
- mPipMenuInputToken = null;
}
/**
@@ -284,13 +286,15 @@ public class PhonePipMenuController implements PipMenuController {
private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
if (DEBUG) {
- Log.d(TAG, "showMenu() state=" + menuState
- + " isMenuVisible=" + isMenuVisible()
- + " allowMenuTimeout=" + allowMenuTimeout
- + " willResizeMenu=" + willResizeMenu
- + " withDelay=" + withDelay
- + " showResizeHandle=" + showResizeHandle
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " allowMenuTimeout=%s"
+ + " willResizeMenu=%s"
+ + " withDelay=%s"
+ + " showResizeHandle=%s"
+ + " callers=\n%s", TAG, menuState, isMenuVisible(), allowMenuTimeout,
+ willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
}
if (!maybeCreateSyncApplier()) {
@@ -382,13 +386,13 @@ public class PhonePipMenuController implements PipMenuController {
private boolean maybeCreateSyncApplier() {
if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
- Log.v(TAG, "Not going to move PiP, either menu or its parent is not created.");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
return false;
}
if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
- mPipMenuInputToken = mPipMenuView.getViewRootImpl().getInputToken();
}
return mApplier != null;
@@ -400,7 +404,8 @@ public class PhonePipMenuController implements PipMenuController {
public void pokeMenu() {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
- Log.d(TAG, "pokeMenu() isMenuVisible=" + isMenuVisible);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: pokeMenu() isMenuVisible=%b", TAG, isMenuVisible);
}
if (isMenuVisible) {
mPipMenuView.pokeMenu();
@@ -410,7 +415,8 @@ public class PhonePipMenuController implements PipMenuController {
private void fadeOutMenu() {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
- Log.d(TAG, "fadeOutMenu() isMenuVisible=" + isMenuVisible);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: fadeOutMenu() isMenuVisible=%b", TAG, isMenuVisible);
}
if (isMenuVisible) {
mPipMenuView.fadeOutMenu();
@@ -436,11 +442,14 @@ public class PhonePipMenuController implements PipMenuController {
public void hideMenu(@PipMenuView.AnimationType int animationType, boolean resize) {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
- Log.d(TAG, "hideMenu() state=" + mMenuState
- + " isMenuVisible=" + isMenuVisible
- + " animationType=" + animationType
- + " resize=" + resize
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " animationType=%s"
+ + " resize=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ animationType, resize,
+ Debug.getCallers(5, " "));
}
if (isMenuVisible) {
mPipMenuView.hideMenu(resize, animationType);
@@ -465,7 +474,8 @@ public class PhonePipMenuController implements PipMenuController {
* Sets the menu actions to the actions provided by the current PiP menu.
*/
@Override
- public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
+ public void setAppActions(ParceledListSlice<RemoteAction> appActions,
+ RemoteAction closeAction) {
mAppActions = appActions;
updateMenuActions();
}
@@ -516,9 +526,11 @@ public class PhonePipMenuController implements PipMenuController {
*/
void onMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
if (DEBUG) {
- Log.d(TAG, "onMenuStateChangeStart() mMenuState=" + mMenuState
- + " menuState=" + menuState + " resize=" + resize
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMenuStateChangeStart() mMenuState=%s"
+ + " menuState=%s resize=%s"
+ + " callers=\n%s", TAG, mMenuState, menuState, resize,
+ Debug.getCallers(5, " "));
}
if (menuState != mMenuState) {
@@ -535,9 +547,11 @@ public class PhonePipMenuController implements PipMenuController {
try {
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */);
+ mSystemWindows.getFocusGrantToken(mPipMenuView),
+ menuState != MENU_STATE_NONE /* grantFocus */);
} catch (RemoteException e) {
- Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus as menu appears/disappears, %s", TAG, e);
}
}
}
@@ -583,9 +597,11 @@ public class PhonePipMenuController implements PipMenuController {
public void updateMenuLayout(Rect bounds) {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
- Log.d(TAG, "updateMenuLayout() state=" + mMenuState
- + " isMenuVisible=" + isMenuVisible
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuLayout() state=%s"
+ + " isMenuVisible=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ Debug.getCallers(5, " "));
}
if (isMenuVisible) {
mPipMenuView.updateMenuLayout(bounds);
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 a41fd8429e35..ad5d85cc083a 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
@@ -46,10 +46,8 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.Log;
import android.util.Pair;
import android.util.Size;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
@@ -61,6 +59,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
@@ -85,10 +84,12 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -136,6 +137,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* @param cornerRadius the pixel value of the corner radius, zero means it's disabled.
*/
void onPipCornerRadiusChanged(int cornerRadius);
+
+ /**
+ * Notifies the listener that user leaves PiP by tapping on the expand button.
+ */
+ void onExpandPip();
}
/**
@@ -143,6 +149,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
*/
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
+ if (mPipTransitionController.handleRotateDisplay(fromRotation, toRotation, t)) {
+ return;
+ }
if (mPipBoundsState.getDisplayLayout().rotation() == toRotation) {
// The same rotation may have been set by auto PiP-able or fixed rotation. So notify
// the change with fromRotation=false to apply the rotated destination bounds from
@@ -225,6 +234,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
true /* saveRestoreSnapFraction */);
}
+
+ @Override
+ public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+ Set<Rect> unrestricted) {
+ if (mPipBoundsState.getDisplayId() == displayId) {
+ mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ }
+ }
};
/**
@@ -246,8 +263,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mMenuController.setAppActions(actions);
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
+ RemoteAction closeAction) {
+ mMenuController.setAppActions(actions, closeAction);
}
@Override
@@ -282,7 +300,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
- Slog.w(TAG, "Device doesn't support Pip feature");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Device doesn't support Pip feature", TAG);
return null;
}
@@ -309,7 +328,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
ShellExecutor mainExecutor
) {
// Ensure that we are the primary user's SystemUI.
- final int processUser = UserManager.get(context).getUserHandle();
+ final int processUser = UserManager.get(context).getProcessUserId();
if (processUser != UserHandle.USER_SYSTEM) {
throw new IllegalStateException("Non-primary Pip component not currently supported.");
}
@@ -375,7 +394,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to register pinned stack listener", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to register pinned stack listener, %s", TAG, e);
}
try {
@@ -387,7 +407,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipInputConsumer.registerInputConsumer();
}
} catch (RemoteException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to register pinned stack listener, %s", TAG, e);
e.printStackTrace();
}
@@ -592,9 +613,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return entryBounds;
}
- private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds,
+ private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
SurfaceControl overlay) {
- mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds, overlay);
+ mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
}
private String getTransitionTag(int direction) {
@@ -636,6 +657,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
+ if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+ mPinnedStackAnimationRecentsCallback.onExpandPip();
+ }
}
}
@@ -724,7 +748,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
.getRootTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedTaskInfo == null) return false;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get RootTaskInfo for pinned task", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get RootTaskInfo for pinned task, %s", TAG, e);
return false;
}
final PipSnapAlgorithm pipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
@@ -870,7 +895,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipController.this.dump(pw);
});
} catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump PipController in 2s");
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to dump PipController in 2s", TAG);
}
}
}
@@ -893,6 +919,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
public void onPipCornerRadiusChanged(int cornerRadius) {
mListener.call(l -> l.onPipCornerRadiusChanged(cornerRadius));
}
+
+ @Override
+ public void onExpandPip() {
+ mListener.call(l -> l.onExpandPip());
+ }
};
IPipImpl(PipController controller) {
@@ -923,11 +954,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ public void stopSwipePipToHome(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay) {
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> {
- controller.stopSwipePipToHome(componentName, destinationBounds, overlay);
+ controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
+ overlay);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 915c5939c34b..11633a91e8c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -20,27 +20,20 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.TransitionDrawable;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.widget.FrameLayout;
import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.DismissView;
import com.android.wm.shell.common.DismissCircleView;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -56,9 +49,6 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
/* The multiplier to apply scale the target size by when applying the magnetic field radius */
private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
- /** Duration of the dismiss scrim fading in/out. */
- private static final int DISMISS_TRANSITION_DURATION_MS = 200;
-
/**
* MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
* PIP.
@@ -69,7 +59,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
* Container for the dismiss circle, so that it can be animated within the container via
* translation rather than within the WindowManager via slow layout animations.
*/
- private ViewGroup mTargetViewContainer;
+ private DismissView mTargetViewContainer;
/** Circle view used to render the dismiss target. */
private DismissCircleView mTargetView;
@@ -79,16 +69,6 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
*/
private MagnetizedObject.MagneticTarget mMagneticTarget;
- /**
- * PhysicsAnimator instance for animating the dismiss target in/out.
- */
- private PhysicsAnimator<View> mMagneticTargetAnimator;
-
- /** Default configuration to use for springing the dismiss target in/out. */
- private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
// Allow dragging the PIP to a location to close it
private boolean mEnableDismissDragToEdge;
@@ -125,12 +105,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
cleanUpDismissTarget();
}
- mTargetView = new DismissCircleView(mContext);
- mTargetViewContainer = new FrameLayout(mContext);
- mTargetViewContainer.setBackgroundDrawable(
- mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition));
- mTargetViewContainer.setClipChildren(false);
- mTargetViewContainer.addView(mTargetView);
+ mTargetViewContainer = new DismissView(mContext);
+ mTargetView = mTargetViewContainer.getCircle();
mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets)) {
mWindowInsets = windowInsets;
@@ -187,7 +163,6 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
}
});
- mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
}
@Override
@@ -213,19 +188,13 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
if (mTargetView == null) {
return;
}
+ if (mTargetViewContainer != null) {
+ mTargetViewContainer.updateResources();
+ }
final Resources res = mContext.getResources();
mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
- final WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- final Insets navInset = insets.getInsetsIgnoringVisibility(
- WindowInsets.Type.navigationBars());
- final FrameLayout.LayoutParams newParams =
- new FrameLayout.LayoutParams(mTargetSize, mTargetSize);
- newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- newParams.bottomMargin = navInset.bottom + mContext.getResources().getDimensionPixelSize(
- R.dimen.floating_dismiss_bottom_margin);
- mTargetView.setLayoutParams(newParams);
// Set the magnetic field radius equal to the target size from the center of the target
setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
@@ -261,7 +230,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
public void createOrUpdateDismissTarget() {
if (!mTargetViewContainer.isAttachedToWindow()) {
- mMagneticTargetAnimator.cancel();
+ mTargetViewContainer.cancelAnimators();
mTargetViewContainer.setVisibility(View.INVISIBLE);
mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
@@ -284,11 +253,11 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
private WindowManager.LayoutParams getDismissTargetLayoutParams() {
final Point windowSize = new Point();
mWindowManager.getDefaultDisplay().getRealSize(windowSize);
-
+ int height = Math.min(windowSize.y, mDismissAreaHeight);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
- mDismissAreaHeight,
- 0, windowSize.y - mDismissAreaHeight,
+ height,
+ 0, windowSize.y - height,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
@@ -312,18 +281,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
createOrUpdateDismissTarget();
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
- mTargetView.setTranslationY(mTargetViewContainer.getHeight());
- mTargetViewContainer.setVisibility(View.VISIBLE);
mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
-
- // Cancel in case we were in the middle of animating it out.
- mMagneticTargetAnimator.cancel();
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
- DISMISS_TRANSITION_DURATION_MS);
+ mTargetViewContainer.show();
}
}
@@ -332,16 +291,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
if (!mEnableDismissDragToEdge) {
return;
}
-
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y,
- mTargetViewContainer.getHeight(),
- mTargetSpringConfig)
- .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
- DISMISS_TRANSITION_DURATION_MS);
+ mTargetViewContainer.hide();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 6e3a20d5f2b2..0f3ff36601fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -22,14 +22,15 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.util.Log;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -141,11 +142,13 @@ public class PipInputConsumer {
mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
} catch (RemoteException e) {
- Log.e(TAG, "Failed to create input consumer", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
// Choreographer.getSfInstance() must be called on the thread that the input event
// receiver should be receiving events
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
Looper.myLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
@@ -165,7 +168,8 @@ public class PipInputConsumer {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
} catch (RemoteException e) {
- Log.e(TAG, "Failed to destroy input consumer", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to destroy input consumer, %s", TAG, e);
}
mInputEventReceiver.dispose();
mInputEventReceiver = null;
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 e1475efcdb57..c0fa8c0a8898 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
@@ -47,7 +47,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.KeyEvent;
@@ -60,10 +59,13 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -119,8 +121,9 @@ public class PipMenuView extends FrameLayout {
private int mBetweenActionPaddingLand;
private AnimatorSet mMenuContainerAnimator;
- private PhonePipMenuController mController;
- private Optional<SplitScreenController> mSplitScreenControllerOptional;
+ private final PhonePipMenuController mController;
+ private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+ private final PipUiEventLogger mPipUiEventLogger;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -150,13 +153,15 @@ public class PipMenuView extends FrameLayout {
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler,
- Optional<SplitScreenController> splitScreenController) {
+ Optional<SplitScreenController> splitScreenController,
+ PipUiEventLogger pipUiEventLogger) {
super(context, null, 0);
mContext = context;
mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mSplitScreenControllerOptional = splitScreenController;
+ mPipUiEventLogger = pipUiEventLogger;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
inflate(context, R.layout.pip_menu, this);
@@ -419,7 +424,7 @@ public class PipMenuView extends FrameLayout {
/**
* @return Estimated minimum {@link Size} to hold the actions.
- * See also {@link #updateActionViews(Rect)}
+ * See also {@link #updateActionViews(Rect)}
*/
Size getEstimatedMinMenuSize() {
final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
@@ -501,7 +506,8 @@ public class PipMenuView extends FrameLayout {
try {
action.getActionIntent().send();
} catch (CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
}
});
}
@@ -539,6 +545,8 @@ public class PipMenuView extends FrameLayout {
// handles the message
hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */,
ANIM_TYPE_HIDE);
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
}
private void dismissPip() {
@@ -547,6 +555,7 @@ public class PipMenuView extends FrameLayout {
// any other dismissal that will update the touch state and fade out the PIP task
// and the menu view at the same time.
mController.onPipDismiss();
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
}
}
@@ -566,6 +575,7 @@ public class PipMenuView extends FrameLayout {
Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second));
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_SETTINGS);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 96fd59f0c911..fa0f0925a08a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -34,12 +34,12 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Looper;
-import android.util.Log;
import android.view.Choreographer;
import androidx.dynamicanimation.animation.AnimationHandler;
import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.animation.PhysicsAnimator;
@@ -49,6 +49,7 @@ import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.function.Consumer;
@@ -94,6 +95,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
@Override
public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
+ // transactions
Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
}
@@ -354,8 +357,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
if (DEBUG) {
- Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exitPip: skipAnimation=%s"
+ + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
@@ -368,7 +372,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
@Override
public void dismissPip() {
if (DEBUG) {
- Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
@@ -552,8 +557,10 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
void animateToOffset(Rect originalBounds, int offset) {
if (DEBUG) {
- Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: animateToOffset: originalBounds=%s offset=%s"
+ + " callers=\n%s", TAG, originalBounds, offset,
+ Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
@@ -671,8 +678,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
private void resizePipUnchecked(Rect toBounds) {
if (DEBUG) {
- Log.d(TAG, "resizePipUnchecked: toBounds=" + toBounds
- + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipUnchecked: toBounds=%s"
+ + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " "));
}
if (!toBounds.equals(getBounds())) {
mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
@@ -684,8 +692,10 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
if (DEBUG) {
- Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds
- + " duration=" + duration + " callers=\n" + Debug.getCallers(5, " "));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
+ + " duration=%s callers=\n%s", TAG, toBounds, duration,
+ Debug.getCallers(5, " "));
}
// Intentionally resize here even if the current bounds match the destination bounds.
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 c816f18c2fc2..abf1a9500e6d 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
@@ -625,6 +625,7 @@ public class PipResizeGestureHandler {
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
super(channel, looper, Choreographer.getSfInstance());
}
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 3ace5f405d36..147a272f4645 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
@@ -35,7 +35,6 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.provider.DeviceConfig;
-import android.util.Log;
import android.util.Size;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -46,6 +45,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -54,6 +54,7 @@ import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -149,7 +150,6 @@ public class PipTouchHandler {
@Override
public void onPipDismiss() {
- mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
mTouchState.removeDoubleTapTimeoutCallback();
mMotionHelper.dismissPip();
}
@@ -1011,7 +1011,8 @@ public class PipTouchHandler {
}
final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
if (estimatedMinMenuSize == null) {
- Log.wtf(TAG, "Failed to get estimated menu size");
+ ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get estimated menu size", TAG);
return false;
}
final Rect currentBounds = mPipBoundsState.getBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 53303ff2b679..d7d69f27f9f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -17,15 +17,15 @@
package com.android.wm.shell.pip.phone;
import android.graphics.PointF;
-import android.os.Handler;
-import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -104,7 +104,8 @@ public class PipTouchState {
mActivePointerId = ev.getPointerId(0);
if (DEBUG) {
- Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Setting active pointer id on DOWN: %d", TAG, mActivePointerId);
}
mLastTouch.set(ev.getRawX(), ev.getRawY());
mDownTouch.set(mLastTouch);
@@ -131,7 +132,8 @@ public class PipTouchState {
addMovementToVelocityTracker(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
- Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on MOVE: %d", TAG, mActivePointerId);
break;
}
@@ -168,8 +170,9 @@ public class PipTouchState {
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
if (DEBUG) {
- Log.e(TAG,
- "Relinquish active pointer id on POINTER_UP: " + mActivePointerId);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Relinquish active pointer id on POINTER_UP: %d",
+ TAG, mActivePointerId);
}
mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
}
@@ -189,7 +192,8 @@ public class PipTouchState {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
- Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on UP: %d", TAG, mActivePointerId);
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
new file mode 100644
index 000000000000..85441af9a870
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module TV pip owner
+galinap@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
new file mode 100644
index 000000000000..1aefd77419aa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_HORIZONTAL;
+import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_UNDETERMINED;
+import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Size;
+import android.view.Gravity;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.Set;
+
+/**
+ * Contains pip bounds calculations that are specific to TV.
+ */
+public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
+
+ private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName();
+ private static final boolean DEBUG = TvPipController.DEBUG;
+
+ private final @NonNull TvPipBoundsState mTvPipBoundsState;
+
+ private int mFixedExpandedHeightInPx;
+ private int mFixedExpandedWidthInPx;
+
+ private final TvPipKeepClearAlgorithm mKeepClearAlgorithm;
+
+ public TvPipBoundsAlgorithm(Context context,
+ @NonNull TvPipBoundsState tvPipBoundsState,
+ @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+ super(context, tvPipBoundsState, pipSnapAlgorithm);
+ this.mTvPipBoundsState = tvPipBoundsState;
+ this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis);
+ reloadResources(context);
+ }
+
+ private void reloadResources(Context context) {
+ final Resources res = context.getResources();
+ mFixedExpandedHeightInPx = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_pictureInPictureExpandedHorizontalHeight);
+ mFixedExpandedWidthInPx = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_pictureInPictureExpandedVerticalWidth);
+ mKeepClearAlgorithm.setPipAreaPadding(
+ res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding));
+ mKeepClearAlgorithm.setMaxRestrictedDistanceFraction(
+ res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1));
+ mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration));
+ }
+
+ @Override
+ public void onConfigurationChanged(Context context) {
+ super.onConfigurationChanged(context);
+ reloadResources(context);
+ }
+
+ /** Returns the destination bounds to place the PIP window on entry. */
+ @Override
+ public Rect getEntryDestinationBounds() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getEntryDestinationBounds()", TAG);
+ }
+ updateExpandedPipSize();
+ final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed();
+ if (isPipExpanded) {
+ updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
+ }
+ mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
+ return getTvPipBounds().getBounds();
+ }
+
+ /** Returns the current bounds adjusted to the new aspect ratio, if valid. */
+ @Override
+ public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
+ }
+ return getTvPipBounds().getBounds();
+ }
+
+ /**
+ * Calculates the PiP bounds.
+ */
+ public Placement getTvPipBounds() {
+ final Size pipSize = getPipSize();
+ final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
+ final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
+ final Rect insetBounds = new Rect();
+ getInsetBounds(insetBounds);
+
+ Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+ Set<Rect> unrestrictedKeepClearAreas = mTvPipBoundsState.getUnrestrictedKeepClearAreas();
+
+ if (mTvPipBoundsState.isImeShowing()) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: IME showing, height: %d",
+ TAG, mTvPipBoundsState.getImeHeight());
+ }
+
+ final Rect imeBounds = new Rect(
+ 0,
+ insetBounds.bottom - mTvPipBoundsState.getImeHeight(),
+ insetBounds.right,
+ insetBounds.bottom);
+
+ unrestrictedKeepClearAreas = new ArraySet<>(unrestrictedKeepClearAreas);
+ unrestrictedKeepClearAreas.add(imeBounds);
+ }
+
+ mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
+ mKeepClearAlgorithm.setScreenSize(screenSize);
+ mKeepClearAlgorithm.setMovementBounds(insetBounds);
+ mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+
+ final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
+ pipSize,
+ restrictedKeepClearAreas,
+ unrestrictedKeepClearAreas);
+
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: screenSize: %s", TAG, screenSize);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: stashOffset: %d", TAG, mTvPipBoundsState.getStashOffset());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: insetBounds: %s", TAG, insetBounds.toShortString());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: pipSize: %s", TAG, pipSize);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: gravity: %s", TAG, Gravity.toString(mTvPipBoundsState.getTvPipGravity()));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: restrictedKeepClearAreas: %s", TAG, restrictedKeepClearAreas);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: unrestrictedKeepClearAreas: %s", TAG, unrestrictedKeepClearAreas);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: placement: %s", TAG, placement);
+ }
+
+ return placement;
+ }
+
+ /**
+ * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not.
+ */
+ int updateGravityOnExpandToggled(int previousGravity, boolean expanding) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravityOnExpandToggled(), expanding: %b"
+ + ", mOrientation: %d, previous gravity: %s",
+ TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation(),
+ Gravity.toString(previousGravity));
+ }
+
+ if (!mTvPipBoundsState.isTvExpandedPipSupported()) {
+ return Gravity.NO_GRAVITY;
+ }
+
+ if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) {
+ float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio();
+ if (expandedRatio == 0) {
+ return Gravity.NO_GRAVITY;
+ }
+ if (expandedRatio < 1) {
+ mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_VERTICAL);
+ } else {
+ mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL);
+ }
+ }
+
+ int gravityToSave = Gravity.NO_GRAVITY;
+ int currentGravity = mTvPipBoundsState.getTvPipGravity();
+ int updatedGravity;
+
+ if (expanding) {
+ // save collapsed gravity
+ gravityToSave = mTvPipBoundsState.getTvPipGravity();
+
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ updatedGravity =
+ Gravity.CENTER_HORIZONTAL | (currentGravity
+ & Gravity.VERTICAL_GRAVITY_MASK);
+ } else {
+ updatedGravity =
+ Gravity.CENTER_VERTICAL | (currentGravity
+ & Gravity.HORIZONTAL_GRAVITY_MASK);
+ }
+ } else {
+ if (previousGravity != Gravity.NO_GRAVITY) {
+ // The pip hasn't been moved since expanding,
+ // go back to previous collapsed position.
+ updatedGravity = previousGravity;
+ } else {
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ updatedGravity =
+ Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ } else {
+ updatedGravity =
+ Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
+ }
+ }
+ }
+ mTvPipBoundsState.setTvPipGravity(updatedGravity);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ }
+
+ return gravityToSave;
+ }
+
+ /**
+ * @return true if gravity changed
+ */
+ boolean updateGravity(int keycode) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravity, keycode: %d", TAG, keycode);
+ }
+
+ // Check if position change is valid
+ if (mTvPipBoundsState.isTvPipExpanded()) {
+ int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
+ if (mOrientation == ORIENTATION_VERTICAL
+ && (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN)
+ || mOrientation == ORIENTATION_HORIZONTAL
+ && (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) {
+ return false;
+ }
+ }
+
+ int currentGravity = mTvPipBoundsState.getTvPipGravity();
+ int updatedGravity;
+ // First axis
+ switch (keycode) {
+ case KEYCODE_DPAD_UP:
+ updatedGravity = Gravity.TOP;
+ break;
+ case KEYCODE_DPAD_DOWN:
+ updatedGravity = Gravity.BOTTOM;
+ break;
+ case KEYCODE_DPAD_LEFT:
+ updatedGravity = Gravity.LEFT;
+ break;
+ case KEYCODE_DPAD_RIGHT:
+ updatedGravity = Gravity.RIGHT;
+ break;
+ default:
+ updatedGravity = currentGravity;
+ }
+
+ // Second axis
+ switch (keycode) {
+ case KEYCODE_DPAD_UP:
+ case KEYCODE_DPAD_DOWN:
+ if (mTvPipBoundsState.isTvPipExpanded()) {
+ updatedGravity |= Gravity.CENTER_HORIZONTAL;
+ } else {
+ updatedGravity |= (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
+ }
+ break;
+ case KEYCODE_DPAD_LEFT:
+ case KEYCODE_DPAD_RIGHT:
+ if (mTvPipBoundsState.isTvPipExpanded()) {
+ updatedGravity |= Gravity.CENTER_VERTICAL;
+ } else {
+ updatedGravity |= (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (updatedGravity != currentGravity) {
+ mTvPipBoundsState.setTvPipGravity(updatedGravity);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private Size getPipSize() {
+ final boolean isExpanded =
+ mTvPipBoundsState.isTvExpandedPipSupported() && mTvPipBoundsState.isTvPipExpanded()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0;
+ if (isExpanded) {
+ return mTvPipBoundsState.getTvExpandedSize();
+ } else {
+ final Rect normalBounds = getNormalBounds();
+ return new Size(normalBounds.width(), normalBounds.height());
+ }
+ }
+
+ /**
+ * Updates {@link TvPipBoundsState#getTvExpandedSize()} based on
+ * {@link TvPipBoundsState#getDesiredTvExpandedAspectRatio()}, the screen size.
+ */
+ void updateExpandedPipSize() {
+ final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
+ final float expandedRatio =
+ mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
+
+ final Size expandedSize;
+ if (expandedRatio == 0) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpandedPipSize(): Expanded mode aspect ratio"
+ + " of 0 not supported", TAG);
+ return;
+ } else if (expandedRatio < 1) {
+ // vertical
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ expandedSize = mTvPipBoundsState.getTvExpandedSize();
+ } else {
+ int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
+ float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
+
+ if (maxHeight > aspectRatioHeight) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Accommodate aspect ratio", TAG);
+ }
+ expandedSize = new Size(mFixedExpandedWidthInPx, (int) aspectRatioHeight);
+ } else {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Aspect ratio is too extreme, use max size", TAG);
+ }
+ expandedSize = new Size(mFixedExpandedWidthInPx, maxHeight);
+ }
+ }
+ } else {
+ // horizontal
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
+ expandedSize = mTvPipBoundsState.getTvExpandedSize();
+ } else {
+ int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
+ float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
+ if (maxWidth > aspectRatioWidth) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Accommodate aspect ratio", TAG);
+ }
+ expandedSize = new Size((int) aspectRatioWidth, mFixedExpandedHeightInPx);
+ } else {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Aspect ratio is too extreme, use max size", TAG);
+ }
+ expandedSize = new Size(maxWidth, mFixedExpandedHeightInPx);
+ }
+ }
+ }
+
+ mTvPipBoundsState.setTvExpandedSize(expandedSize);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpandedPipSize(): expanded size, width: %d, height: %d",
+ TAG, expandedSize.getWidth(), expandedSize.getHeight());
+ }
+ }
+
+ void keepUnstashedForCurrentKeepClearAreas() {
+ mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas();
+ }
+}
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
new file mode 100644
index 000000000000..986554853034
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.util.Size;
+import android.view.Gravity;
+
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * TV specific values of the current state of the PIP bounds.
+ */
+public class TvPipBoundsState extends PipBoundsState {
+
+ public static final int ORIENTATION_UNDETERMINED = 0;
+ public static final int ORIENTATION_VERTICAL = 1;
+ public static final int ORIENTATION_HORIZONTAL = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ORIENTATION_"}, value = {
+ ORIENTATION_UNDETERMINED,
+ ORIENTATION_VERTICAL,
+ ORIENTATION_HORIZONTAL
+ })
+ public @interface Orientation {
+ }
+
+ public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+
+ private final boolean mIsTvExpandedPipSupported;
+ private boolean mIsTvPipExpanded;
+ private boolean mTvPipManuallyCollapsed;
+ private float mDesiredTvExpandedAspectRatio;
+ private @Orientation int mTvFixedPipOrientation;
+ private int mTvPipGravity;
+ private @Nullable Size mTvExpandedSize;
+
+
+ public TvPipBoundsState(@NonNull Context context) {
+ super(context);
+ mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
+ }
+
+ /**
+ * Initialize states when first entering PiP.
+ */
+ @Override
+ public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
+ super.setBoundsStateForEntry(componentName, activityInfo, params, pipBoundsAlgorithm);
+ setDesiredTvExpandedAspectRatio(params.getExpandedAspectRatioFloat(), true);
+ }
+
+ /** Resets the TV PiP state for a new activity. */
+ public void resetTvPipState() {
+ mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
+ mTvPipGravity = DEFAULT_TV_GRAVITY;
+ }
+
+ /** Set the tv expanded bounds of PIP */
+ public void setTvExpandedSize(@Nullable Size size) {
+ mTvExpandedSize = size;
+ }
+
+ /** Get the expanded size of the PiP. */
+ @Nullable
+ public Size getTvExpandedSize() {
+ return mTvExpandedSize;
+ }
+
+ /** Set the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
+ public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
+ if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED || aspectRatio == 0) {
+ mDesiredTvExpandedAspectRatio = aspectRatio;
+ resetTvPipState();
+ return;
+ }
+ if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
+ || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)) {
+ mDesiredTvExpandedAspectRatio = aspectRatio;
+ }
+ }
+
+ /** Get the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
+ public float getDesiredTvExpandedAspectRatio() {
+ return mDesiredTvExpandedAspectRatio;
+ }
+
+ /** Sets the orientation the expanded TV PiP activity has been fixed to. */
+ public void setTvFixedPipOrientation(@Orientation int orientation) {
+ mTvFixedPipOrientation = orientation;
+ }
+
+ /** Returns the fixed orientation of the expanded PiP on TV. */
+ @Orientation
+ public int getTvFixedPipOrientation() {
+ return mTvFixedPipOrientation;
+ }
+
+ /** Sets the current gravity of the TV PiP. */
+ public void setTvPipGravity(int gravity) {
+ mTvPipGravity = gravity;
+ }
+
+ /** Returns the current gravity of the TV PiP. */
+ public int getTvPipGravity() {
+ return mTvPipGravity;
+ }
+
+ /** Sets whether the TV PiP is currently expanded. */
+ public void setTvPipExpanded(boolean expanded) {
+ mIsTvPipExpanded = expanded;
+ }
+
+ /** Returns whether the TV PiP is currently expanded. */
+ public boolean isTvPipExpanded() {
+ return mIsTvPipExpanded;
+ }
+
+ /** Sets whether the user has manually collapsed the TV PiP. */
+ public void setTvPipManuallyCollapsed(boolean collapsed) {
+ mTvPipManuallyCollapsed = collapsed;
+ }
+
+ /** Returns whether the user has manually collapsed the TV PiP. */
+ public boolean isTvPipManuallyCollapsed() {
+ return mTvPipManuallyCollapsed;
+ }
+
+ /** Returns whether expanded PiP is supported by the device. */
+ public boolean isTvExpandedPipSupported() {
+ return mIsTvExpandedPipSupported;
+ }
+
+}
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 00083d986dbe..46b8e6098273 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.TaskInfo;
import android.content.ComponentName;
@@ -30,42 +31,49 @@ import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
-import android.util.Log;
-import android.view.DisplayInfo;
+import android.view.Gravity;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
- TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
+ TvPipMenuController.Delegate, TvPipNotificationController.Delegate,
+ DisplayController.OnDisplaysChangedListener {
private static final String TAG = "TvPipController";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
+ private static final double EPS = 1e-7;
private static final int NONEXISTENT_TASK_ID = -1;
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "STATE_" }, value = {
+ @IntDef(prefix = {"STATE_"}, value = {
STATE_NO_PIP,
STATE_PIP,
- STATE_PIP_MENU
+ STATE_PIP_MENU,
})
public @interface State {}
@@ -87,65 +95,79 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final Context mContext;
- private final PipBoundsState mPipBoundsState;
- private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final TvPipBoundsState mTvPipBoundsState;
+ private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
+ private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
+ private Runnable mUnstashRunnable;
+
+ private RemoteAction mCloseAction;
+ // How long the shell will wait for the app to close the PiP if a custom action is set.
+ private int mPipForceCloseDelay;
private int mResizeAnimationDuration;
public static Pip create(
Context context,
- PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsState tvPipBoundsState,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
+ DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
return new TvPipController(
context,
- pipBoundsState,
- pipBoundsAlgorithm,
+ tvPipBoundsState,
+ tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
pipMediaController,
pipNotificationController,
taskStackListener,
+ displayController,
wmShell,
- mainExecutor).mImpl;
+ mainExecutor,
+ mainHandler).mImpl;
}
private TvPipController(
Context context,
- PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsState tvPipBoundsState,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
+ DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
mContext = context;
mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
- mPipBoundsState = pipBoundsState;
- mPipBoundsState.setDisplayId(context.getDisplayId());
- mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
- mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mTvPipBoundsState = tvPipBoundsState;
+ mTvPipBoundsState.setDisplayId(context.getDisplayId());
+ mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+ mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mPipMediaController = pipMediaController;
@@ -162,18 +184,26 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
registerTaskStackListenerCallback(taskStackListener);
registerWmShellPinnedStackListener(wmShell);
+ displayController.addDisplayWindowListener(this);
}
private void onConfigurationChanged(Configuration newConfig) {
- if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
+ }
if (isPipShown()) {
- if (DEBUG) Log.d(TAG, " > closing Pip.");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > closing Pip.", TAG);
+ }
closePip();
}
loadConfigurations();
mPipNotificationController.onConfigurationChanged(mContext);
+ mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
/**
@@ -190,26 +220,38 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
*/
@Override
public void showPictureInPictureMenu() {
- if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
+ }
- if (mState != STATE_PIP) {
- if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state.");
+ if (mState == STATE_NO_PIP) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > cannot open Menu from the current state.", TAG);
+ }
return;
}
setState(STATE_PIP_MENU);
- resizePinnedStack(STATE_PIP_MENU);
+ mTvPipMenuController.showMenu();
+ updatePinnedStackBounds();
}
- /**
- * Moves Pip window to its "normal" position.
- */
@Override
- public void movePipToNormalPosition() {
- if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState));
-
+ public void onMenuClosed() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closeMenu(), state before=%s", TAG, stateToName(mState));
+ }
setState(STATE_PIP);
- resizePinnedStack(STATE_PIP);
+ mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas();
+ updatePinnedStackBounds();
+ }
+
+ @Override
+ public void onInMoveModeChanged() {
+ updatePinnedStackBounds();
}
/**
@@ -217,59 +259,153 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
*/
@Override
public void movePipToFullscreen() {
- if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
+ }
mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
onPipDisappeared();
}
- /**
- * Closes Pip window.
- */
@Override
- public void closePip() {
- if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState));
+ public void togglePipExpansion() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: togglePipExpansion()", TAG);
+ }
+ boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
+ int saveGravity = mTvPipBoundsAlgorithm
+ .updateGravityOnExpandToggled(mPreviousGravity, expanding);
+ if (saveGravity != Gravity.NO_GRAVITY) {
+ mPreviousGravity = saveGravity;
+ }
+ mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
+ mTvPipBoundsState.setTvPipExpanded(expanding);
+ updatePinnedStackBounds();
+ }
- removeTask(mPinnedTaskId);
- onPipDisappeared();
+ @Override
+ public void enterPipMovementMenu() {
+ setState(STATE_PIP_MENU);
+ mTvPipMenuController.showMovementMenuOnly();
+ }
+
+ @Override
+ public void movePip(int keycode) {
+ if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
+ mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
+ mPreviousGravity = Gravity.NO_GRAVITY;
+ updatePinnedStackBounds();
+ } else {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Position hasn't changed", TAG);
+ }
+ }
+ }
+
+ @Override
+ public int getPipGravity() {
+ return mTvPipBoundsState.getTvPipGravity();
+ }
+
+ public int getOrientation() {
+ return mTvPipBoundsState.getTvFixedPipOrientation();
+ }
+
+ @Override
+ public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+ Set<Rect> unrestricted) {
+ if (mTvPipBoundsState.getDisplayId() == displayId) {
+ mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ updatePinnedStackBounds();
+ }
}
/**
- * Resizes the Pip task/window to the appropriate size for the given state.
- * This is a legacy API. Now we expect that the state argument passed to it should always match
- * the current state of the Controller. If it does not match an {@link IllegalArgumentException}
- * will be thrown. However, if the passed state does match - we'll determine the right bounds
- * to the state and will move Pip task/window there.
- *
- * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
- * state of the Controller.
+ * Update the PiP bounds based on the state of the PiP and keep clear areas.
+ * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
*/
- private void resizePinnedStack(@State int state) {
- if (state != mState) {
- throw new IllegalArgumentException("The passed state should match the current state!");
+ private void updatePinnedStackBounds() {
+ if (mState == STATE_NO_PIP) {
+ return;
}
- if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState));
- final Rect newBounds;
- switch (mState) {
- case STATE_PIP_MENU:
- newBounds = mPipBoundsState.getExpandedBounds();
- break;
+ final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode();
+ final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition;
+ final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds();
- case STATE_PIP:
- // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
- // Internally, it will get the "default" bounds from PipBoundsState and adjust them
- // as needed to account for things like IME state (will query PipBoundsState for
- // this information as well, so it's important to keep PipBoundsState up to date).
- newBounds = mPipBoundsAlgorithm.getNormalBounds();
- break;
+ int stashType =
+ disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType();
+ mTvPipBoundsState.setStashed(stashType);
- case STATE_NO_PIP:
- default:
- return;
+ if (stayAtAnchorPosition) {
+ movePinnedStackTo(placement.getAnchorBounds());
+ } else if (disallowStashing) {
+ movePinnedStackTo(placement.getUnstashedBounds());
+ } else {
+ movePinnedStackTo(placement.getBounds());
+ }
+
+ if (mUnstashRunnable != null) {
+ mMainHandler.removeCallbacks(mUnstashRunnable);
+ mUnstashRunnable = null;
+ }
+ if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
+ mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+ mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
+ }
+ }
+
+ /** Animates the PiP to the given bounds. */
+ private void movePinnedStackTo(Rect bounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
+ }
+ mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
+ mResizeAnimationDuration, rect -> {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePinnedStack() animation done", TAG);
+ }
+ mTvPipMenuController.updateExpansionState();
+ });
+ }
+
+ /**
+ * Closes Pip window.
+ */
+ @Override
+ public void closePip() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
+ mCloseAction);
}
- mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
+ if (mCloseAction != null) {
+ try {
+ mCloseAction.getActionIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send close action, %s", TAG, e);
+ }
+ mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
+ } else {
+ closeCurrentPiP(mPinnedTaskId);
+ }
+ }
+
+ private void closeCurrentPiP(int pinnedTaskId) {
+ if (mPinnedTaskId != pinnedTaskId) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: PiP has already been closed by custom close action", TAG);
+ return;
+ }
+ removeTask(mPinnedTaskId);
+ onPipDisappeared();
}
private void registerSessionListenerForCurrentUser() {
@@ -278,57 +414,75 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private void checkIfPinnedTaskAppeared() {
final TaskInfo pinnedTask = getPinnedTaskInfo();
- if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask);
+ }
if (pinnedTask == null || pinnedTask.topActivity == null) return;
mPinnedTaskId = pinnedTask.taskId;
- setState(STATE_PIP);
mPipMediaController.onActivityPinned();
mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
}
private void checkIfPinnedTaskIsGone() {
- if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onTaskStackChanged()", TAG);
+ }
if (isPipShown() && getPinnedTaskInfo() == null) {
- Log.w(TAG, "Pinned task is gone.");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Pinned task is gone.", TAG);
onPipDisappeared();
}
}
private void onPipDisappeared() {
- if (DEBUG) Log.d(TAG, "onPipDisappeared() state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
+ }
mPipNotificationController.dismiss();
- mTvPipMenuController.hideMenu();
+ mTvPipMenuController.closeMenu();
+ mTvPipBoundsState.resetTvPipState();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@Override
public void onPipTransitionStarted(int direction, Rect pipBounds) {
- if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
+ }
}
@Override
public void onPipTransitionCanceled(int direction) {
- if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
+ }
}
@Override
public void onPipTransitionFinished(int direction) {
- if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
-
- if (mState == STATE_PIP_MENU) {
- if (DEBUG) Log.d(TAG, " > show menu");
- mTvPipMenuController.showMenu();
+ if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) {
+ setState(STATE_PIP);
+ }
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
}
}
private void setState(@State int state) {
if (DEBUG) {
- Log.d(TAG, "setState(), state=" + stateToName(state) + ", prev="
- + stateToName(mState));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setState(), state=%s, prev=%s",
+ TAG, stateToName(state), stateToName(mState));
}
mState = state;
}
@@ -336,17 +490,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private void loadConfigurations() {
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
- // "Cache" bounds for the Pip menu as "expanded" bounds in PipBoundsState. We'll refer back
- // to this value in resizePinnedStack(), when we are adjusting Pip task/window position for
- // the menu.
- mPipBoundsState.setExpandedBounds(
- Rect.unflattenFromString(res.getString(R.string.pip_menu_bounds)));
- }
-
- private DisplayInfo getDisplayInfo() {
- final DisplayInfo displayInfo = new DisplayInfo();
- mContext.getDisplay().getDisplayInfo(displayInfo);
- return displayInfo;
+ mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
@@ -365,7 +509,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (task.getWindowingMode() == WINDOWING_MODE_PINNED) {
- if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPinnedActivityRestartAttempt()", TAG);
+ }
// If the "Pip-ed" Activity is launched again by Launcher or intent, make it
// fullscreen.
@@ -381,21 +528,86 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (DEBUG) {
- Log.d(TAG, "onImeVisibilityChanged(), visible=" + imeVisible
- + ", height=" + imeHeight);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onImeVisibilityChanged(), visible=%b, height=%d",
+ TAG, imeVisible, imeHeight);
}
- if (imeVisible == mPipBoundsState.isImeShowing()
- && (!imeVisible || imeHeight == mPipBoundsState.getImeHeight())) {
+ if (imeVisible == mTvPipBoundsState.isImeShowing()
+ && (!imeVisible || imeHeight == mTvPipBoundsState.getImeHeight())) {
// Nothing changed: either IME has been and remains invisible, or remains
// visible with the same height.
return;
}
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- // "Normal" Pip bounds may have changed, so if we are in the "normal" state,
- // let's update the bounds.
- if (mState == STATE_PIP) {
- resizePinnedStack(STATE_PIP);
+ mTvPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+
+ if (mState != STATE_NO_PIP) {
+ updatePinnedStackBounds();
+ }
+ }
+
+ @Override
+ public void onAspectRatioChanged(float ratio) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onAspectRatioChanged: %f", TAG, ratio);
+ }
+
+ boolean ratioChanged = mTvPipBoundsState.getAspectRatio() != ratio;
+ mTvPipBoundsState.setAspectRatio(ratio);
+
+ if (!mTvPipBoundsState.isTvPipExpanded() && ratioChanged) {
+ updatePinnedStackBounds();
+ }
+ }
+
+ @Override
+ public void onExpandedAspectRatioChanged(float ratio) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
+ }
+
+ // 0) No update to the ratio --> don't do anything
+
+ if (Math.abs(mTvPipBoundsState.getDesiredTvExpandedAspectRatio() - ratio)
+ < EPS) {
+ return;
+ }
+
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
+
+ // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
+ // --> update bounds, but don't toggle
+ if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) {
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
+ updatePinnedStackBounds();
+ }
+
+ // 2) PiP is expanded, but expanded PiP was disabled
+ // --> collapse PiP
+ if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
+ int saveGravity = mTvPipBoundsAlgorithm
+ .updateGravityOnExpandToggled(mPreviousGravity, false);
+ if (saveGravity != Gravity.NO_GRAVITY) {
+ mPreviousGravity = saveGravity;
+ }
+ mTvPipBoundsState.setTvPipExpanded(false);
+ updatePinnedStackBounds();
+ }
+
+ // 3) PiP not expanded and not manually collapsed and expand was enabled
+ // --> expand to new ratio
+ if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
+ int saveGravity = mTvPipBoundsAlgorithm
+ .updateGravityOnExpandToggled(mPreviousGravity, true);
+ if (saveGravity != Gravity.NO_GRAVITY) {
+ mPreviousGravity = saveGravity;
+ }
+ mTvPipBoundsState.setTvPipExpanded(true);
+ updatePinnedStackBounds();
}
}
@@ -403,36 +615,53 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
@Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onActionsChanged()");
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
+ RemoteAction closeAction) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActionsChanged()", TAG);
+ }
- mTvPipMenuController.setAppActions(actions);
+ mTvPipMenuController.setAppActions(actions, closeAction);
+ mCloseAction = closeAction;
}
});
} catch (RemoteException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to register pinned stack listener, %s", TAG, e);
}
}
private static TaskInfo getPinnedTaskInfo() {
- if (DEBUG) Log.d(TAG, "getPinnedTaskInfo()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getPinnedTaskInfo()", TAG);
+ }
try {
final TaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (DEBUG) Log.d(TAG, " > taskInfo=" + taskInfo);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: taskInfo=%s", TAG, taskInfo);
+ }
return taskInfo;
} catch (RemoteException e) {
- Log.e(TAG, "getRootTaskInfo() failed", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getRootTaskInfo() failed, %s", TAG, e);
return null;
}
}
private static void removeTask(int taskId) {
- if (DEBUG) Log.d(TAG, "removeTask(), taskId=" + taskId);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removeTask(), taskId=%d", TAG, taskId);
+ }
try {
ActivityTaskManager.getService().removeTask(taskId);
} catch (Exception e) {
- Log.e(TAG, "Atm.removeTask() failed", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Atm.removeTask() failed, %s", TAG, e);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
new file mode 100644
index 000000000000..927c1ec2a888
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * All interpolators needed for TV specific Pip animations
+ */
+public class TvPipInterpolators {
+
+ /**
+ * A standard ease-in-out curve reserved for moments of interaction (button and card states).
+ */
+ public static final Interpolator STANDARD = new PathInterpolator(0.2f, 0.1f, 0f, 1f);
+
+ /**
+ * A sharp ease-out-expo curve created for snappy but fluid browsing between cards and clusters.
+ */
+ public static final Interpolator BROWSE = new PathInterpolator(0.18f, 1f, 0.22f, 1f);
+
+ /**
+ * A smooth ease-out-expo curve created for incoming elements (forward, back, overlay).
+ */
+ public static final Interpolator ENTER = new PathInterpolator(0.12f, 1f, 0.4f, 1f);
+
+ /**
+ * A smooth ease-in-out-expo curve created for outgoing elements (forward, back, overlay).
+ */
+ public static final Interpolator EXIT = new PathInterpolator(0.4f, 1f, 0.12f, 1f);
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
new file mode 100644
index 000000000000..5ac7a7200494
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -0,0 +1,741 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Size
+import android.view.Gravity
+import com.android.wm.shell.pip.PipBoundsState
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+private const val DEFAULT_PIP_MARGINS = 48
+private const val DEFAULT_STASH_DURATION = 5000L
+private const val RELAX_DEPTH = 1
+private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15
+
+/**
+ * This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking
+ * into account app defined keep clear areas.
+ *
+ * @param clock A function returning a current timestamp (in milliseconds)
+ */
+class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
+ /**
+ * Result of the positioning algorithm.
+ *
+ * @param bounds The bounds the PiP should be placed at
+ * @param anchorBounds The bounds of the PiP anchor position
+ * (where the PiP would be placed if there were no keep clear areas)
+ * @param stashType Where the PiP has been stashed, if at all
+ * @param unstashDestinationBounds If stashed, the PiP should move to this position after
+ * [stashDuration] has passed.
+ * @param unstashTime If stashed, the time at which the PiP should move
+ * to [unstashDestinationBounds]
+ */
+ data class Placement(
+ val bounds: Rect,
+ val anchorBounds: Rect,
+ @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+ val unstashDestinationBounds: Rect? = null,
+ val unstashTime: Long = 0L
+ ) {
+ /** Bounds to use if the PiP should not be stashed. */
+ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
+ }
+
+ /** The size of the screen */
+ private var screenSize = Size(0, 0)
+
+ /** The bounds the PiP is allowed to move in */
+ private var movementBounds = Rect()
+
+ /** Padding to add between a keep clear area that caused the PiP to move and the PiP */
+ var pipAreaPadding = DEFAULT_PIP_MARGINS
+
+ /** The distance the PiP peeks into the screen when stashed */
+ var stashOffset = DEFAULT_PIP_MARGINS
+
+ /**
+ * How long (in milliseconds) the PiP should stay stashed for after the last time the
+ * keep clear areas causing the PiP to stash have changed.
+ */
+ var stashDuration = DEFAULT_STASH_DURATION
+
+ /** The fraction of screen width/height restricted keep clear areas can move the PiP */
+ var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION
+
+ private var pipGravity = Gravity.BOTTOM or Gravity.RIGHT
+ private var transformedScreenBounds = Rect()
+ private var transformedMovementBounds = Rect()
+
+ private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
+ private var lastStashTime: Long = Long.MIN_VALUE
+
+ /**
+ * Calculates the position the PiP should be placed at, taking into consideration the
+ * given keep clear areas.
+ *
+ * Restricted keep clear areas can move the PiP only by a limited amount, and may be ignored
+ * if there is no space for the PiP to move to.
+ * Apps holding the permission [android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS]
+ * can declare unrestricted keep clear areas, which can move the PiP farther and placement will
+ * always try to respect these areas.
+ *
+ * If no free space the PiP is allowed to move to can be found, a stashed position is returned
+ * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has
+ * passed as [Placement.unstashDestinationBounds].
+ *
+ * @param pipSize The size of the PiP window
+ * @param restrictedAreas The restricted keep clear areas
+ * @param unrestrictedAreas The unrestricted keep clear areas
+ *
+ */
+ fun calculatePipPosition(
+ pipSize: Size,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Placement {
+ val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
+ val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
+ val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
+
+ val result = calculatePipPositionTransformed(
+ pipAnchorBounds,
+ transformedRestrictedAreas,
+ transformedUnrestrictedAreas
+ )
+
+ val screenSpaceBounds = fromTransformedSpace(result.bounds)
+ return Placement(
+ screenSpaceBounds,
+ fromTransformedSpace(result.anchorBounds),
+ getStashType(screenSpaceBounds, movementBounds),
+ result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+ result.unstashTime
+ )
+ }
+
+ /**
+ * Filters out areas that encompass the entire movement bounds and returns them mapped to
+ * the base case space.
+ *
+ * Areas encompassing the entire movement bounds can occur when a full-screen View gets focused,
+ * but we don't want this to cause the PiP to get stashed.
+ */
+ private fun transformAndFilterAreas(areas: Set<Rect>): Set<Rect> {
+ return areas.mapNotNullTo(mutableSetOf()) {
+ when {
+ it.contains(movementBounds) -> null
+ else -> toTransformedSpace(it)
+ }
+ }
+ }
+
+ /**
+ * Calculates the position the PiP should be placed at, taking into consideration the
+ * given keep clear areas.
+ * All parameters are transformed from screen space to the base case space, where the PiP
+ * anchor is in the bottom right corner / on the right side.
+ *
+ * @see [calculatePipPosition]
+ */
+ private fun calculatePipPositionTransformed(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Placement {
+ if (restrictedAreas.isEmpty() && unrestrictedAreas.isEmpty()) {
+ return Placement(pipAnchorBounds, pipAnchorBounds)
+ }
+
+ // First try to find a free position to move to
+ val freeMovePos = findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ if (freeMovePos != null) {
+ lastAreasOverlappingUnstashPosition = emptySet()
+ return Placement(freeMovePos, pipAnchorBounds)
+ }
+
+ // If no free position is found, we have to stash the PiP.
+ // Find the position the PiP should return to once it unstashes by doing a relaxed
+ // search, or ignoring restricted areas, or returning to the anchor position
+ val unstashBounds =
+ findRelaxedMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ ?: findFreeMovePosition(pipAnchorBounds, emptySet(), unrestrictedAreas)
+ ?: pipAnchorBounds
+
+ val keepClearAreas = restrictedAreas + unrestrictedAreas
+ val areasOverlappingUnstashPosition =
+ keepClearAreas.filter { Rect.intersects(it, unstashBounds) }.toSet()
+ val areasOverlappingUnstashPositionChanged =
+ !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
+ lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition
+
+ val now = clock()
+ if (areasOverlappingUnstashPositionChanged) {
+ lastStashTime = now
+ }
+
+ // If overlapping areas haven't changed and the stash duration has passed, we can
+ // place the PiP at the unstash position
+ val unstashTime = lastStashTime + stashDuration
+ if (now >= unstashTime) {
+ return Placement(unstashBounds, pipAnchorBounds)
+ }
+
+ // Otherwise, we'll stash it close to the unstash position
+ val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas)
+ return Placement(
+ stashedBounds,
+ pipAnchorBounds,
+ getStashType(stashedBounds, transformedMovementBounds),
+ unstashBounds,
+ unstashTime
+ )
+ }
+
+ @PipBoundsState.StashType
+ private fun getStashType(stashedBounds: Rect, movementBounds: Rect): Int {
+ return when {
+ stashedBounds.left < movementBounds.left -> STASH_TYPE_LEFT
+ stashedBounds.right > movementBounds.right -> STASH_TYPE_RIGHT
+ stashedBounds.top < movementBounds.top -> STASH_TYPE_TOP
+ stashedBounds.bottom > movementBounds.bottom -> STASH_TYPE_BOTTOM
+ else -> STASH_TYPE_NONE
+ }
+ }
+
+ private fun findRelaxedMovePosition(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ if (RELAX_DEPTH <= 0) {
+ // relaxed search disabled
+ return null
+ }
+
+ return findRelaxedMovePosition(
+ RELAX_DEPTH,
+ pipAnchorBounds,
+ restrictedAreas.toMutableSet(),
+ unrestrictedAreas
+ )
+ }
+
+ private fun findRelaxedMovePosition(
+ depth: Int,
+ pipAnchorBounds: Rect,
+ restrictedAreas: MutableSet<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ if (depth == 0) {
+ return findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ }
+
+ val candidates = mutableListOf<Rect>()
+ val areasToExclude = restrictedAreas.toList()
+ for (area in areasToExclude) {
+ restrictedAreas.remove(area)
+ val candidate = findRelaxedMovePosition(
+ depth - 1,
+ pipAnchorBounds,
+ restrictedAreas,
+ unrestrictedAreas
+ )
+ restrictedAreas.add(area)
+
+ if (candidate != null) {
+ candidates.add(candidate)
+ }
+ }
+ return candidates.minByOrNull { candidateCost(it, pipAnchorBounds) }
+ }
+
+ /** Cost function to evaluate candidate bounds */
+ private fun candidateCost(candidateBounds: Rect, pipAnchorBounds: Rect): Int {
+ // squared euclidean distance of corresponding rect corners
+ val dx = candidateBounds.left - pipAnchorBounds.left
+ val dy = candidateBounds.top - pipAnchorBounds.top
+ return dx * dx + dy * dy
+ }
+
+ private fun findFreeMovePosition(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ val movementBounds = transformedMovementBounds
+ val candidateEdgeRects = mutableListOf<Rect>()
+ val minRestrictedLeft =
+ pipAnchorBounds.right - screenSize.width * maxRestrictedDistanceFraction
+
+ candidateEdgeRects.add(
+ movementBounds.offsetCopy(movementBounds.width() + pipAreaPadding, 0)
+ )
+ candidateEdgeRects.addAll(unrestrictedAreas)
+ candidateEdgeRects.addAll(restrictedAreas.filter { it.left >= minRestrictedLeft })
+
+ // throw out edges that are too close to the left screen edge to fit the PiP
+ val minLeft = movementBounds.left + pipAnchorBounds.width()
+ candidateEdgeRects.retainAll { it.left - pipAreaPadding > minLeft }
+ candidateEdgeRects.sortBy { -it.left }
+
+ val maxRestrictedDY = (screenSize.height * maxRestrictedDistanceFraction).roundToInt()
+
+ val candidateBounds = mutableListOf<Rect>()
+ for (edgeRect in candidateEdgeRects) {
+ val edge = edgeRect.left - pipAreaPadding
+ val dx = (edge - pipAnchorBounds.width()) - pipAnchorBounds.left
+ val candidatePipBounds = pipAnchorBounds.offsetCopy(dx, 0)
+ val searchUp = true
+ val searchDown = !isPipAnchoredToCorner()
+
+ if (searchUp) {
+ val event = findMinMoveUp(candidatePipBounds, restrictedAreas, unrestrictedAreas)
+ val padding = if (event.start) 0 else pipAreaPadding
+ val dy = event.pos - pipAnchorBounds.bottom - padding
+ val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY
+ val candidate = pipAnchorBounds.offsetCopy(dx, dy)
+ val isOnScreen = candidate.top > movementBounds.top
+ val hangingMidAir = !candidate.intersectsY(edgeRect)
+ if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) {
+ candidateBounds.add(candidate)
+ }
+ }
+
+ if (searchDown) {
+ val event = findMinMoveDown(candidatePipBounds, restrictedAreas, unrestrictedAreas)
+ val padding = if (event.start) 0 else pipAreaPadding
+ val dy = event.pos - pipAnchorBounds.top + padding
+ val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY
+ val candidate = pipAnchorBounds.offsetCopy(dx, dy)
+ val isOnScreen = candidate.bottom < movementBounds.bottom
+ val hangingMidAir = !candidate.intersectsY(edgeRect)
+ if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) {
+ candidateBounds.add(candidate)
+ }
+ }
+ }
+
+ candidateBounds.sortBy { candidateCost(it, pipAnchorBounds) }
+ return candidateBounds.firstOrNull()
+ }
+
+ private fun getNearbyStashedPosition(bounds: Rect, keepClearAreas: Set<Rect>): Rect {
+ val screenBounds = transformedScreenBounds
+ val stashCandidates = Array(2) { Rect(bounds) }
+ val areasOverlappingPipX = keepClearAreas.filter { it.intersectsX(bounds) }
+ val areasOverlappingPipY = keepClearAreas.filter { it.intersectsY(bounds) }
+
+ if (screenBounds.bottom - bounds.bottom <= bounds.top - screenBounds.top) {
+ // bottom is closer than top, stash downwards
+ val fullStashTop = screenBounds.bottom - stashOffset
+
+ val maxBottom = areasOverlappingPipX.maxByOrNull { it.bottom }!!.bottom
+ val partialStashTop = maxBottom + pipAreaPadding
+
+ val downPosition = stashCandidates[0]
+ downPosition.offsetTo(bounds.left, min(fullStashTop, partialStashTop))
+ } else {
+ // top is closer than bottom, stash upwards
+ val fullStashY = screenBounds.top - bounds.height() + stashOffset
+
+ val minTop = areasOverlappingPipX.minByOrNull { it.top }!!.top
+ val partialStashY = minTop - bounds.height() - pipAreaPadding
+
+ val upPosition = stashCandidates[0]
+ upPosition.offsetTo(bounds.left, max(fullStashY, partialStashY))
+ }
+
+ if (screenBounds.right - bounds.right <= bounds.left - screenBounds.left) {
+ // right is closer than left, stash rightwards
+ val fullStashLeft = screenBounds.right - stashOffset
+
+ val maxRight = areasOverlappingPipY.maxByOrNull { it.right }!!.right
+ val partialStashLeft = maxRight + pipAreaPadding
+
+ val rightPosition = stashCandidates[1]
+ rightPosition.offsetTo(min(fullStashLeft, partialStashLeft), bounds.top)
+ } else {
+ // left is closer than right, stash leftwards
+ val fullStashLeft = screenBounds.left - bounds.width() + stashOffset
+
+ val minLeft = areasOverlappingPipY.minByOrNull { it.left }!!.left
+ val partialStashLeft = minLeft - bounds.width() - pipAreaPadding
+
+ val rightPosition = stashCandidates[1]
+ rightPosition.offsetTo(max(fullStashLeft, partialStashLeft), bounds.top)
+ }
+
+ return stashCandidates.minByOrNull {
+ val dx = abs(it.left - bounds.left)
+ val dy = abs(it.top - bounds.top)
+ dx * bounds.height() + dy * bounds.width()
+ }!!
+ }
+
+ /**
+ * Prevents the PiP from being stashed for the current set of keep clear areas.
+ * The PiP may stash again if keep clear areas change.
+ */
+ fun keepUnstashedForCurrentKeepClearAreas() {
+ lastStashTime = Long.MIN_VALUE
+ }
+
+ /**
+ * Updates the size of the screen.
+ *
+ * @param size The new size of the screen
+ */
+ fun setScreenSize(size: Size) {
+ if (screenSize == size) {
+ return
+ }
+
+ screenSize = size
+ transformedScreenBounds =
+ toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height))
+ transformedMovementBounds = toTransformedSpace(transformedMovementBounds)
+ }
+
+ /**
+ * Updates the bounds within which the PiP is allowed to move.
+ *
+ * @param bounds The new movement bounds
+ */
+ fun setMovementBounds(bounds: Rect) {
+ if (movementBounds == bounds) {
+ return
+ }
+
+ movementBounds.set(bounds)
+ transformedMovementBounds = toTransformedSpace(movementBounds)
+ }
+
+ /**
+ * Sets the corner/side of the PiP's home position.
+ */
+ fun setGravity(gravity: Int) {
+ if (pipGravity == gravity) return
+
+ pipGravity = gravity
+ transformedScreenBounds =
+ toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height))
+ transformedMovementBounds = toTransformedSpace(movementBounds)
+ }
+
+ /**
+ * @param open Whether this event marks the opening of an occupied segment
+ * @param pos The coordinate of this event
+ * @param unrestricted Whether this event was generated by an unrestricted keep clear area
+ * @param start Marks the special start event. Earlier events are skipped when sweeping
+ */
+ data class SweepLineEvent(
+ val open: Boolean,
+ val pos: Int,
+ val unrestricted: Boolean,
+ val start: Boolean = false
+ )
+
+ /**
+ * Returns a [SweepLineEvent] representing the minimal move up from [pipBounds] that clears
+ * the given keep clear areas.
+ */
+ private fun findMinMoveUp(
+ pipBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): SweepLineEvent {
+ val events = mutableListOf<SweepLineEvent>()
+ val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted ->
+ { area ->
+ if (pipBounds.intersectsX(area)) {
+ events.add(SweepLineEvent(true, area.bottom, unrestricted))
+ events.add(SweepLineEvent(false, area.top, unrestricted))
+ }
+ }
+ }
+
+ restrictedAreas.forEach(generateEvents(false))
+ unrestrictedAreas.forEach(generateEvents(true))
+
+ return sweepLineFindEarliestGap(
+ events,
+ pipBounds.height() + pipAreaPadding,
+ pipBounds.bottom,
+ pipBounds.height()
+ )
+ }
+
+ /**
+ * Returns a [SweepLineEvent] representing the minimal move down from [pipBounds] that clears
+ * the given keep clear areas.
+ */
+ private fun findMinMoveDown(
+ pipBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): SweepLineEvent {
+ val events = mutableListOf<SweepLineEvent>()
+ val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted ->
+ { area ->
+ if (pipBounds.intersectsX(area)) {
+ events.add(SweepLineEvent(true, -area.top, unrestricted))
+ events.add(SweepLineEvent(false, -area.bottom, unrestricted))
+ }
+ }
+ }
+
+ restrictedAreas.forEach(generateEvents(false))
+ unrestrictedAreas.forEach(generateEvents(true))
+
+ val earliestEvent = sweepLineFindEarliestGap(
+ events,
+ pipBounds.height() + pipAreaPadding,
+ -pipBounds.top,
+ pipBounds.height()
+ )
+
+ return earliestEvent.copy(pos = -earliestEvent.pos)
+ }
+
+ /**
+ * Takes a list of events representing the starts & ends of occupied segments, and
+ * returns the earliest event whose position is unoccupied and has [gapSize] distance to the
+ * next event.
+ *
+ * @param events List of [SweepLineEvent] representing occupied segments
+ * @param gapSize Size of the gap to search for
+ * @param startPos The position to start the search on.
+ * Inserts a special event marked with [SweepLineEvent.start].
+ * @param startGapSize Used instead of [gapSize] for the start event
+ */
+ private fun sweepLineFindEarliestGap(
+ events: MutableList<SweepLineEvent>,
+ gapSize: Int,
+ startPos: Int,
+ startGapSize: Int
+ ): SweepLineEvent {
+ events.add(
+ SweepLineEvent(
+ open = false,
+ pos = startPos,
+ unrestricted = true,
+ start = true
+ )
+ )
+ events.sortBy { -it.pos }
+
+ // sweep
+ var openCount = 0
+ var i = 0
+ while (i < events.size) {
+ val event = events[i]
+ if (!event.start) {
+ if (event.open) {
+ openCount++
+ } else {
+ openCount--
+ }
+ }
+
+ if (openCount == 0) {
+ // check if placement is possible
+ val candidate = event.pos
+ if (candidate > startPos) {
+ i++
+ continue
+ }
+
+ val eventGapSize = if (event.start) startGapSize else gapSize
+ val nextEvent = events.getOrNull(i + 1)
+ if (nextEvent == null || nextEvent.pos < candidate - eventGapSize) {
+ return event
+ }
+ }
+ i++
+ }
+
+ return events.last()
+ }
+
+ private fun shouldTransformFlipX(): Boolean {
+ return when (pipGravity) {
+ (Gravity.TOP), (Gravity.TOP or Gravity.CENTER_HORIZONTAL) -> true
+ (Gravity.TOP or Gravity.LEFT) -> true
+ (Gravity.LEFT), (Gravity.LEFT or Gravity.CENTER_VERTICAL) -> true
+ (Gravity.BOTTOM or Gravity.LEFT) -> true
+ else -> false
+ }
+ }
+
+ private fun shouldTransformFlipY(): Boolean {
+ return when (pipGravity) {
+ (Gravity.TOP or Gravity.LEFT) -> true
+ (Gravity.TOP or Gravity.RIGHT) -> true
+ else -> false
+ }
+ }
+
+ private fun shouldTransformRotate(): Boolean {
+ val horizontalGravity = pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+ val leftOrRight = horizontalGravity == Gravity.LEFT || horizontalGravity == Gravity.RIGHT
+
+ if (leftOrRight) return false
+ return when (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) {
+ (Gravity.TOP) -> true
+ (Gravity.BOTTOM) -> true
+ else -> false
+ }
+ }
+
+ /**
+ * Transforms the given rect from screen space into the base case space, where the PiP
+ * anchor is positioned in the bottom right corner or on the right side (for expanded PiP).
+ *
+ * @see [fromTransformedSpace]
+ */
+ private fun toTransformedSpace(r: Rect): Rect {
+ var screenWidth = screenSize.width
+ var screenHeight = screenSize.height
+
+ val tl = Point(r.left, r.top)
+ val tr = Point(r.right, r.top)
+ val br = Point(r.right, r.bottom)
+ val bl = Point(r.left, r.bottom)
+ val corners = arrayOf(tl, tr, br, bl)
+
+ // rotate first (CW)
+ if (shouldTransformRotate()) {
+ corners.forEach { p ->
+ val px = p.x
+ val py = p.y
+ p.x = py
+ p.y = -px
+ p.y += screenWidth // shift back screen into positive quadrant
+ }
+ screenWidth = screenSize.height
+ screenHeight = screenSize.width
+ }
+
+ // flip second
+ corners.forEach {
+ if (shouldTransformFlipX()) it.x = screenWidth - it.x
+ if (shouldTransformFlipY()) it.y = screenHeight - it.y
+ }
+
+ val top = corners.minByOrNull { it.y }!!.y
+ val right = corners.maxByOrNull { it.x }!!.x
+ val bottom = corners.maxByOrNull { it.y }!!.y
+ val left = corners.minByOrNull { it.x }!!.x
+
+ return Rect(left, top, right, bottom)
+ }
+
+ /**
+ * Transforms the given rect from the base case space, where the PiP anchor is positioned in
+ * the bottom right corner or on the right side, back into screen space.
+ *
+ * @see [toTransformedSpace]
+ */
+ private fun fromTransformedSpace(r: Rect): Rect {
+ val rotate = shouldTransformRotate()
+ val transformedScreenWidth = if (rotate) screenSize.height else screenSize.width
+ val transformedScreenHeight = if (rotate) screenSize.width else screenSize.height
+
+ val tl = Point(r.left, r.top)
+ val tr = Point(r.right, r.top)
+ val br = Point(r.right, r.bottom)
+ val bl = Point(r.left, r.bottom)
+ val corners = arrayOf(tl, tr, br, bl)
+
+ // flip first
+ corners.forEach {
+ if (shouldTransformFlipX()) it.x = transformedScreenWidth - it.x
+ if (shouldTransformFlipY()) it.y = transformedScreenHeight - it.y
+ }
+
+ // rotate second (CCW)
+ if (rotate) {
+ corners.forEach { p ->
+ p.y -= screenSize.width // undo shift back screen into positive quadrant
+ val px = p.x
+ val py = p.y
+ p.x = -py
+ p.y = px
+ }
+ }
+
+ val top = corners.minByOrNull { it.y }!!.y
+ val right = corners.maxByOrNull { it.x }!!.x
+ val bottom = corners.maxByOrNull { it.y }!!.y
+ val left = corners.minByOrNull { it.x }!!.x
+
+ return Rect(left, top, right, bottom)
+ }
+
+ /** PiP anchor bounds in base case for given gravity */
+ private fun getNormalPipAnchorBounds(pipSize: Size, movementBounds: Rect): Rect {
+ var size = pipSize
+ val rotateCW = shouldTransformRotate()
+ if (rotateCW) {
+ size = Size(pipSize.height, pipSize.width)
+ }
+
+ val pipBounds = Rect()
+ if (isPipAnchoredToCorner()) {
+ // bottom right
+ Gravity.apply(
+ Gravity.BOTTOM or Gravity.RIGHT,
+ size.width,
+ size.height,
+ movementBounds,
+ pipBounds
+ )
+ return pipBounds
+ } else {
+ // expanded, right side
+ Gravity.apply(Gravity.RIGHT, size.width, size.height, movementBounds, pipBounds)
+ return pipBounds
+ }
+ }
+
+ private fun isPipAnchoredToCorner(): Boolean {
+ val left = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
+ val right = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.RIGHT
+ val top = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP
+ val bottom = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM
+
+ val horizontal = left || right
+ val vertical = top || bottom
+
+ return horizontal && vertical
+ }
+
+ private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
+ private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
+ private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
index 6f7cd82f8da0..abbc614b4b4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.pip.tv;
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -26,7 +24,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
-import android.widget.TextView;
import com.android.wm.shell.R;
@@ -36,12 +33,7 @@ import com.android.wm.shell.R;
*/
public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
private final ImageView mIconImageView;
- private final ImageView mButtonImageView;
- private final TextView mDescriptionTextView;
- private Animator mTextFocusGainAnimator;
- private Animator mButtonFocusGainAnimator;
- private Animator mTextFocusLossAnimator;
- private Animator mButtonFocusLossAnimator;
+ private final View mButtonView;
private OnClickListener mOnClickListener;
public TvPipMenuActionButton(Context context) {
@@ -64,8 +56,7 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
inflater.inflate(R.layout.tv_pip_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
- mButtonImageView = findViewById(R.id.button);
- mDescriptionTextView = findViewById(R.id.desc);
+ mButtonView = findViewById(R.id.button);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
@@ -74,45 +65,18 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
setImageResource(typedArray.getResourceId(0, 0));
final int textResId = typedArray.getResourceId(1, 0);
if (textResId != 0) {
- setTextAndDescription(getContext().getString(textResId));
+ setTextAndDescription(textResId);
}
-
typedArray.recycle();
}
@Override
- public void onFinishInflate() {
- super.onFinishInflate();
- mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> {
- if (hasFocus) {
- startFocusGainAnimation();
- } else {
- startFocusLossAnimation();
- }
- });
-
- mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_gain_animation);
- mTextFocusGainAnimator.setTarget(mDescriptionTextView);
- mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_gain_animation);
- mButtonFocusGainAnimator.setTarget(mButtonImageView);
-
- mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_loss_animation);
- mTextFocusLossAnimator.setTarget(mDescriptionTextView);
- mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_loss_animation);
- mButtonFocusLossAnimator.setTarget(mButtonImageView);
- }
-
- @Override
public void setOnClickListener(OnClickListener listener) {
// We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to
// the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
// listener to the ImageView.
mOnClickListener = listener;
- mButtonImageView.setOnClickListener(listener != null ? this : null);
+ mButtonView.setOnClickListener(listener != null ? this : null);
}
@Override
@@ -143,55 +107,34 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
* Sets the text for description the with the given string.
*/
public void setTextAndDescription(CharSequence text) {
- mButtonImageView.setContentDescription(text);
- mDescriptionTextView.setText(text);
- }
-
- private static void cancelAnimator(Animator animator) {
- if (animator.isStarted()) {
- animator.cancel();
- }
+ mButtonView.setContentDescription(text);
}
/**
- * Starts the focus gain animation.
+ * Sets the text and description with the given string resource id.
*/
- public void startFocusGainAnimation() {
- cancelAnimator(mButtonFocusLossAnimator);
- cancelAnimator(mTextFocusLossAnimator);
- mTextFocusGainAnimator.start();
- if (mButtonImageView.getAlpha() < 1f) {
- // If we had faded out the ripple drawable, run our manual focus change animation.
- // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
- // animator.
- mButtonFocusGainAnimator.start();
- }
+ public void setTextAndDescription(int resId) {
+ setTextAndDescription(getContext().getString(resId));
}
- /**
- * Starts the focus loss animation.
- */
- public void startFocusLossAnimation() {
- cancelAnimator(mButtonFocusGainAnimator);
- cancelAnimator(mTextFocusGainAnimator);
- mTextFocusLossAnimator.start();
- if (mButtonImageView.hasFocus()) {
- // Button uses ripple that has the default animation for the focus changes.
- // However, it doesn't expose the API to fade out while it is focused, so we should
- // manually run the fade out animation when PIP controls row loses focus.
- mButtonFocusLossAnimator.start();
- }
+ @Override
+ public void setEnabled(boolean enabled) {
+ mButtonView.setEnabled(enabled);
}
- /**
- * Resets to initial state.
- */
- public void reset() {
- cancelAnimator(mButtonFocusGainAnimator);
- cancelAnimator(mTextFocusGainAnimator);
- cancelAnimator(mButtonFocusLossAnimator);
- cancelAnimator(mTextFocusLossAnimator);
- mButtonImageView.setAlpha(1f);
- mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
+ @Override
+ public boolean isEnabled() {
+ return mButtonView.isEnabled();
+ }
+
+ void setIsCustomCloseAction(boolean isCustomCloseAction) {
+ mIconImageView.setImageTintList(
+ getResources().getColorStateList(
+ isCustomCloseAction ? R.color.tv_pip_menu_close_icon
+ : R.color.tv_pip_menu_icon));
+ mButtonView.setBackgroundTintList(getResources()
+ .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
+ : R.color.tv_pip_menu_icon_bg));
}
+
}
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 ee41b41a743d..35c34ac8315f 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,25 +18,34 @@ package com.android.wm.shell.pip.tv;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
-import android.util.Log;
+import android.os.RemoteException;
import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -47,21 +56,47 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final Context mContext;
private final SystemWindows mSystemWindows;
- private final PipBoundsState mPipBoundsState;
+ private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
private Delegate mDelegate;
private SurfaceControl mLeash;
- private TvPipMenuView mMenuView;
+ private TvPipMenuView mPipMenuView;
+
+ // 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;
private final List<RemoteAction> mMediaActions = new ArrayList<>();
private final List<RemoteAction> mAppActions = new ArrayList<>();
+ private RemoteAction mCloseAction;
+
+ private SyncRtSurfaceTransactionApplier mApplier;
+ RectF mTmpSourceRectF = new RectF();
+ RectF mTmpDestinationRectF = new RectF();
+ Matrix mMoveTransform = new Matrix();
+
+ private final float[] mTmpValues = new float[9];
+ private final Runnable mUpdateEmbeddedMatrix = () -> {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ return;
+ }
+ mMoveTransform.getValues(mTmpValues);
+ try {
+ mPipMenuView.getViewRootImpl().getAccessibilityEmbeddedConnection()
+ .setScreenMatrix(mTmpValues);
+ } catch (RemoteException e) {
+ if (DEBUG) e.printStackTrace();
+ }
+ };
- public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
+ public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
Handler mainHandler) {
mContext = context;
- mPipBoundsState = pipBoundsState;
+ mTvPipBoundsState = tvPipBoundsState;
mSystemWindows = systemWindows;
mMainHandler = mainHandler;
@@ -70,18 +105,21 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
final BroadcastReceiver closeSystemDialogsBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- hideMenu();
+ closeMenu();
}
};
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
- mainHandler);
+ mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged);
}
void setDelegate(Delegate delegate) {
- if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setDelegate(), delegate=%s", TAG, delegate);
+ }
if (mDelegate != null) {
throw new IllegalStateException(
"The delegate has already been set and should not change.");
@@ -104,94 +142,190 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void attachPipMenuView() {
- if (DEBUG) Log.d(TAG, "attachPipMenuView()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: attachPipMenuView()", TAG);
+ }
- if (mMenuView != null) {
+ if (mPipMenuView != null) {
detachPipMenuView();
}
- mMenuView = new TvPipMenuView(mContext);
- mMenuView.setListener(this);
- mSystemWindows.addView(mMenuView,
+ mPipMenuView = new TvPipMenuView(mContext);
+ mPipMenuView.setListener(this);
+ mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
}
+ void showMovementMenuOnly() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMovementMenuOnly()", TAG);
+ }
+ mInMoveMode = true;
+ mCloseAfterExitMoveMenu = true;
+ showMenuInternal();
+ }
+
@Override
public void showMenu() {
- if (DEBUG) Log.d(TAG, "showMenu()");
-
- if (mMenuView != null) {
- mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
- mPipBoundsState.getDisplayBounds().width(),
- mPipBoundsState.getDisplayBounds().height()));
- maybeUpdateMenuViewActions();
- mMenuView.show();
-
- // By default, SystemWindows views are above everything else.
- // Set the relative z-order so the menu is below PiP.
- if (mMenuView.getWindowSurfaceControl() != null && mLeash != null) {
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, -1);
- t.apply();
- }
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenu()", TAG);
}
+ mInMoveMode = false;
+ mCloseAfterExitMoveMenu = false;
+ showMenuInternal();
}
- void hideMenu() {
- hideMenu(true);
+ private void showMenuInternal() {
+ if (mPipMenuView == null) {
+ return;
+ }
+ Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
+ mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
+ MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+ maybeUpdateMenuViewActions();
+ updateExpansionState();
+
+ SurfaceControl menuSurfaceControl = getSurfaceControl();
+ if (menuSurfaceControl != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
+ t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
+ t.apply();
+ }
+ grantPipMenuFocus(true);
+ if (mInMoveMode) {
+ mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ } else {
+ mPipMenuView.showButtonMenu();
+ }
+ }
+
+ void updateGravity(int gravity) {
+ mPipMenuView.showMovementHints(gravity);
}
- void hideMenu(boolean movePipWindow) {
- if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow);
+ void updateExpansionState() {
+ mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
+ mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
+ }
+
+ private Rect getMenuBounds(Rect pipBounds) {
+ int extraSpaceInPx = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+ Rect menuBounds = new Rect(pipBounds);
+ menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
+ return menuBounds;
+ }
- if (!isMenuVisible()) {
+ void closeMenu() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closeMenu()", TAG);
+ }
+ if (mPipMenuView == null) {
return;
}
- mMenuView.hide();
- if (movePipWindow) {
- mDelegate.movePipToNormalPosition();
- }
+ mPipMenuView.hideAll();
+ grantPipMenuFocus(false);
+ mDelegate.onMenuClosed();
+ }
+
+ boolean isInMoveMode() {
+ return mInMoveMode;
}
@Override
- public void detach() {
- hideMenu();
- detachPipMenuView();
- mLeash = null;
+ public void onEnterMoveMode() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
+ mCloseAfterExitMoveMenu);
+ }
+ mInMoveMode = true;
+ mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
}
- private void detachPipMenuView() {
- if (DEBUG) Log.d(TAG, "detachPipMenuView()");
+ @Override
+ public boolean onExitMoveMode() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
+ mCloseAfterExitMoveMenu);
+ }
+ if (mCloseAfterExitMoveMenu) {
+ mInMoveMode = false;
+ mCloseAfterExitMoveMenu = false;
+ closeMenu();
+ return true;
+ }
+ if (mInMoveMode) {
+ mInMoveMode = false;
+ mPipMenuView.showButtonMenu();
+ return true;
+ }
+ return false;
+ }
- if (mMenuView == null) {
- return;
+ @Override
+ public boolean onPipMovement(int keycode) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - %b", TAG, mInMoveMode);
}
+ if (mInMoveMode) {
+ mDelegate.movePip(keycode);
+ }
+ return mInMoveMode;
+ }
- mSystemWindows.removeView(mMenuView);
- mMenuView = null;
+ @Override
+ public void detach() {
+ closeMenu();
+ detachPipMenuView();
+ mLeash = null;
}
@Override
- public void setAppActions(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "setAppActions()");
- updateAdditionalActionsList(mAppActions, actions.getList());
+ public void setAppActions(ParceledListSlice<RemoteAction> actions, RemoteAction closeAction) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAppActions()", TAG);
+ }
+ updateAdditionalActionsList(mAppActions, actions.getList(), closeAction);
}
private void onMediaActionsChanged(List<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onMediaActionsChanged()");
- updateAdditionalActionsList(mMediaActions, actions);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMediaActionsChanged()", TAG);
+ }
+
+ // Hide disabled actions.
+ List<RemoteAction> enabledActions = new ArrayList<>();
+ for (RemoteAction remoteAction : actions) {
+ if (remoteAction.isEnabled()) {
+ enabledActions.add(remoteAction);
+ }
+ }
+ updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
}
- private void updateAdditionalActionsList(
- List<RemoteAction> destination, @Nullable List<RemoteAction> source) {
+ private void updateAdditionalActionsList(List<RemoteAction> destination,
+ @Nullable List<RemoteAction> source, RemoteAction closeAction) {
final int number = source != null ? source.size() : 0;
- if (number == 0 && destination.isEmpty()) {
+ if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
// Nothing changed.
return;
}
+ mCloseAction = closeAction;
+
destination.clear();
if (number > 0) {
destination.addAll(source);
@@ -200,24 +334,180 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void maybeUpdateMenuViewActions() {
- if (mMenuView == null) {
+ if (mPipMenuView == null) {
return;
}
if (!mAppActions.isEmpty()) {
- mMenuView.setAdditionalActions(mAppActions, mMainHandler);
+ mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
} else {
- mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
+ mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
}
}
@Override
public boolean isMenuVisible() {
- return mMenuView != null && mMenuView.isVisible();
+ boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: isMenuVisible: %b", TAG, isVisible);
+ }
+ return isVisible;
+ }
+
+ /**
+ * Does an immediate window crop of the PiP menu.
+ */
+ @Override
+ public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
+ @android.annotation.Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipMenu: %s", TAG, destinationBounds.toShortString());
+ }
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!maybeCreateSyncApplier()) {
+ return;
+ }
+
+ SurfaceControl surfaceControl = getSurfaceControl();
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
+ .withWindowCrop(getMenuBounds(destinationBounds))
+ .build();
+ if (pipLeash != null && t != null) {
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ .withMergeTransaction(t)
+ .build();
+ mApplier.scheduleApply(params, pipParams);
+ } else {
+ mApplier.scheduleApply(params);
+ }
+ }
+
+ private SurfaceControl getSurfaceControl() {
+ return mSystemWindows.getViewSurface(mPipMenuView);
+ }
+
+ @Override
+ public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
+ Rect pipDestBounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipMenu: %s", TAG, pipDestBounds.toShortString());
+ }
+
+ if (pipDestBounds.isEmpty()) {
+ if (transaction == null && DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: no transaction given", TAG);
+ }
+ return;
+ }
+ if (!maybeCreateSyncApplier()) {
+ return;
+ }
+
+ Rect menuDestBounds = getMenuBounds(pipDestBounds);
+ Rect mTmpSourceBounds = new Rect();
+ // If there is no pip leash supplied, that means the PiP leash is already finalized
+ // resizing and the PiP menu is also resized. We then want to do a scale from the current
+ // new menu bounds.
+ if (pipLeash != null && transaction != null) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+ }
+ mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+ } else {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: mTmpSourceBounds based on menu width and height", TAG);
+ }
+ mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+ }
+
+ mTmpSourceRectF.set(mTmpSourceBounds);
+ mTmpDestinationRectF.set(menuDestBounds);
+ mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+
+ SurfaceControl surfaceControl = getSurfaceControl();
+ SyncRtSurfaceTransactionApplier.SurfaceParams params =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceControl)
+ .withMatrix(mMoveTransform)
+ .build();
+
+ if (pipLeash != null && transaction != null) {
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ .withMergeTransaction(transaction)
+ .build();
+ mApplier.scheduleApply(params, pipParams);
+ } else {
+ mApplier.scheduleApply(params);
+ }
+
+ if (mPipMenuView.getViewRootImpl() != null) {
+ mPipMenuView.getHandler().removeCallbacks(mUpdateEmbeddedMatrix);
+ mPipMenuView.getHandler().post(mUpdateEmbeddedMatrix);
+ }
+
+ updateMenuBounds(pipDestBounds);
+ }
+
+ private boolean maybeCreateSyncApplier() {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
+ return false;
+ }
+
+ if (mApplier == null) {
+ mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
+ }
+ return true;
+ }
+
+ private void detachPipMenuView() {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ mApplier = null;
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
+ }
+
+ @Override
+ public void updateMenuBounds(Rect destinationBounds) {
+ Rect menuBounds = getMenuBounds(destinationBounds);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
+ }
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
+ if (mPipMenuView != null) {
+ mPipMenuView.updateLayout(destinationBounds);
+ }
+ }
+
+ @Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
}
@Override
public void onBackPress() {
- hideMenu();
+ if (!onExitMoveMode()) {
+ closeMenu();
+ }
}
@Override
@@ -230,9 +520,39 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mDelegate.movePipToFullscreen();
}
+ @Override
+ public void onToggleExpandedMode() {
+ mDelegate.togglePipExpansion();
+ }
+
interface Delegate {
- void movePipToNormalPosition();
void movePipToFullscreen();
+
+ void movePip(int keycode);
+
+ void onInMoveModeChanged();
+
+ int getPipGravity();
+
+ void togglePipExpansion();
+
+ void onMenuClosed();
+
void closePip();
}
+
+ private void grantPipMenuFocus(boolean grantFocus) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: grantWindowFocus(%b)", TAG, grantFocus);
+ }
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
+ } catch (Exception e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus, %s", TAG, e);
+ }
+ }
}
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 d6cd9ea13ca1..9529d04fe185 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
@@ -16,54 +16,71 @@
package com.android.wm.shell.pip.tv;
-import static android.animation.AnimatorInflater.loadAnimator;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+import static android.view.KeyEvent.KEYCODE_ENTER;
-import android.animation.Animator;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
-import android.graphics.Color;
+import android.graphics.Rect;
import android.os.Handler;
-import android.os.Looper;
import android.util.AttributeSet;
-import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
- * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
- * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
- * a {@link #setAdditionalActions(List, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
+ * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
+ * via a {@link #setAdditionalActions(List, Handler)} call.
*/
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView";
private static final boolean DEBUG = TvPipController.DEBUG;
- private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
- private final Animator mFadeInAnimation;
- private final Animator mFadeOutAnimation;
- @Nullable private Listener mListener;
+ @Nullable
+ private Listener mListener;
private final LinearLayout mActionButtonsContainer;
+ private final View mMenuFrameView;
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final ImageView mArrowUp;
+ private final ImageView mArrowRight;
+ private final ImageView mArrowDown;
+ private final ImageView mArrowLeft;
+
+ private final ViewGroup mScrollView;
+ private final ViewGroup mHorizontalScrollView;
+
+ private Rect mCurrentBounds;
+
+ private final TvPipMenuActionButton mExpandButton;
+ private final TvPipMenuActionButton mCloseButton;
+
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -85,67 +102,172 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
.setOnClickListener(this);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
+
+ mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
+ mCloseButton.setOnClickListener(this);
+ mCloseButton.setIsCustomCloseAction(true);
+
+ mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
.setOnClickListener(this);
+ mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
+ mExpandButton.setOnClickListener(this);
- mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
- mFadeInAnimation.setTarget(mActionButtonsContainer);
+ mScrollView = findViewById(R.id.tv_pip_menu_scroll);
+ mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
- mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
- mFadeOutAnimation.setTarget(mActionButtonsContainer);
+ mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
+
+ mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
+ mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
+ mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
+ mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
+ }
+
+ void updateLayout(Rect updatedBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
+ boolean previouslyVertical =
+ mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
+ boolean vertical = updatedBounds.height() > updatedBounds.width();
+
+ mCurrentBounds = updatedBounds;
+ if (previouslyVertical == vertical) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: no update for menu layout", TAG);
+ }
+ return;
+ } else {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: change menu layout to vertical: %b", TAG, vertical);
+ }
+ }
+
+ if (vertical) {
+ mHorizontalScrollView.removeView(mActionButtonsContainer);
+ mScrollView.addView(mActionButtonsContainer);
+ } else {
+ mScrollView.removeView(mActionButtonsContainer);
+ mHorizontalScrollView.addView(mActionButtonsContainer);
+ }
+ mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
+ : LinearLayout.HORIZONTAL);
+
+ mScrollView.setVisibility(vertical ? VISIBLE : GONE);
+ mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
}
void setListener(@Nullable Listener listener) {
mListener = listener;
}
- void show() {
- if (DEBUG) Log.d(TAG, "show()");
-
- mFadeInAnimation.start();
- setAlpha(1.0f);
- grantWindowFocus(true);
+ void setExpandedModeEnabled(boolean enabled) {
+ mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
}
- void hide() {
- if (DEBUG) Log.d(TAG, "hide()");
+ void setIsExpanded(boolean expanded) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setIsExpanded, expanded: %b", TAG, expanded);
+ }
+ mExpandButton.setImageResource(
+ expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+ mExpandButton.setTextAndDescription(
+ expanded ? R.string.pip_collapse : R.string.pip_expand);
+ }
- mFadeOutAnimation.start();
- setAlpha(0.0f);
- grantWindowFocus(false);
+ /**
+ * @param gravity for the arrow hints
+ */
+ void showMoveMenu(int gravity) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
+ }
+ showMenuButtons(false);
+ showMovementHints(gravity);
+ showMenuFrame(true);
}
- boolean isVisible() {
- return getAlpha() == 1.0f;
+ void showButtonMenu() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
+ }
+ showMenuButtons(true);
+ hideMovementHints();
+ showMenuFrame(true);
}
- private void grantWindowFocus(boolean grantFocus) {
- if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")");
+ /**
+ * Hides all menu views, including the menu frame.
+ */
+ void hideAll() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+ }
+ showMenuButtons(false);
+ hideMovementHints();
+ showMenuFrame(false);
+ }
- try {
- WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- getViewRootImpl().getInputToken(), grantFocus);
- } catch (Exception e) {
- Log.e(TAG, "Unable to update focus", e);
+ private void animateAlphaTo(float alpha, View view) {
+ if (view.getAlpha() == alpha) {
+ return;
}
+ view.animate()
+ .alpha(alpha)
+ .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
+ .setDuration(500)
+ .withStartAction(() -> {
+ if (alpha != 0) {
+ view.setVisibility(VISIBLE);
+ }
+ })
+ .withEndAction(() -> {
+ if (alpha == 0) {
+ view.setVisibility(GONE);
+ }
+ });
}
- void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
- if (DEBUG) Log.d(TAG, "setAdditionalActions()");
+ boolean isVisible() {
+ return mMenuFrameView.getAlpha() != 0f
+ || mActionButtonsContainer.getAlpha() != 0f
+ || mArrowUp.getAlpha() != 0f
+ || mArrowRight.getAlpha() != 0f
+ || mArrowDown.getAlpha() != 0f
+ || mArrowLeft.getAlpha() != 0f;
+ }
+
+ void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
+ Handler mainHandler) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAdditionalActions()", TAG);
+ }
+
+ // Replace system close action with custom close action if available
+ if (closeAction != null) {
+ setActionForButton(closeAction, mCloseButton, mainHandler);
+ } else {
+ mCloseButton.setTextAndDescription(R.string.pip_close);
+ mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
+ }
+ mCloseButton.setIsCustomCloseAction(closeAction != null);
+ // Make sure the close action is always enabled
+ mCloseButton.setEnabled(true);
// Make sure we exactly as many additional buttons as we have actions to display.
final int actionsNumber = actions.size();
int buttonsNumber = mAdditionalButtons.size();
if (actionsNumber > buttonsNumber) {
- final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
// Add buttons until we have enough to display all of the actions.
while (actionsNumber > buttonsNumber) {
- final TvPipMenuActionButton button = (TvPipMenuActionButton) layoutInflater.inflate(
- R.layout.tv_pip_menu_additional_action_button, mActionButtonsContainer,
- false);
+ TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
button.setOnClickListener(this);
- mActionButtonsContainer.addView(button);
+ mActionButtonsContainer.addView(button,
+ mActionButtonsContainer.getChildCount() - 1);
mAdditionalButtons.add(button);
buttonsNumber++;
@@ -165,19 +287,37 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
for (int index = 0; index < actionsNumber; index++) {
final RemoteAction action = actions.get(index);
final TvPipMenuActionButton button = mAdditionalButtons.get(index);
- button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- button.setTextAndDescription(action.getContentDescription());
- button.setEnabled(action.isEnabled());
- button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
- button.setTag(action);
- action.getIcon().loadDrawableAsync(mContext, drawable -> {
- drawable.setTint(Color.WHITE);
- button.setImageDrawable(drawable);
- }, mainHandler);
+ // Remove action if it matches the custom close action.
+ if (actionsMatch(action, closeAction)) {
+ button.setVisibility(GONE);
+ continue;
+ }
+ setActionForButton(action, button, mainHandler);
}
}
+ /**
+ * Checks whether title, description and intent match.
+ * Comparing icons would be good, but using equals causes false negatives
+ */
+ private boolean actionsMatch(RemoteAction action1, RemoteAction action2) {
+ if (action1 == action2) return true;
+ if (action1 == null || action2 == null) return false;
+ return Objects.equals(action1.getTitle(), action2.getTitle())
+ && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
+ && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
+ }
+
+ private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
+ Handler mainHandler) {
+ button.setVisibility(View.VISIBLE); // Ensure the button is visible.
+ button.setTextAndDescription(action.getContentDescription());
+ button.setEnabled(action.isEnabled());
+ button.setTag(action);
+ action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
+ }
+
@Nullable
SurfaceControl getWindowSurfaceControl() {
final ViewRootImpl root = getViewRootImpl();
@@ -198,8 +338,12 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
final int id = v.getId();
if (id == R.id.tv_pip_menu_fullscreen_button) {
mListener.onFullscreenButtonClick();
+ } else if (id == R.id.tv_pip_menu_move_button) {
+ mListener.onEnterMoveMode();
} else if (id == R.id.tv_pip_menu_close_button) {
mListener.onCloseButtonClick();
+ } else if (id == R.id.tv_pip_menu_expand_button) {
+ mListener.onToggleExpandedMode();
} else {
// This should be an "additional action"
final RemoteAction action = (RemoteAction) v.getTag();
@@ -207,27 +351,115 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
try {
action.getActionIntent().send();
} catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
}
} else {
- Log.w(TAG, "RemoteAction is null");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: RemoteAction is null", TAG);
}
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK
- && mListener != null) {
- mListener.onBackPress();
- return true;
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: dispatchKeyEvent, action: %d, keycode: %d",
+ TAG, event.getAction(), event.getKeyCode());
+ }
+ if (mListener != null && event.getAction() == ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KEYCODE_BACK:
+ mListener.onBackPress();
+ return true;
+ case KEYCODE_DPAD_UP:
+ case KEYCODE_DPAD_DOWN:
+ case KEYCODE_DPAD_LEFT:
+ case KEYCODE_DPAD_RIGHT:
+ return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
+ event);
+ case KEYCODE_ENTER:
+ case KEYCODE_DPAD_CENTER:
+ return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
+ default:
+ break;
+ }
}
return super.dispatchKeyEvent(event);
}
+ /**
+ * Shows user hints for moving the PiP, e.g. arrows.
+ */
+ public void showMovementHints(int gravity) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
+ }
+
+ 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);
+ }
+
+ private boolean checkGravity(int gravity, int feature) {
+ return (gravity & feature) == feature;
+ }
+
+ /**
+ * Hides user hints for moving the PiP, e.g. arrows.
+ */
+ public void hideMovementHints() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMovementHints()", TAG);
+ }
+ animateAlphaTo(0, mArrowUp);
+ animateAlphaTo(0, mArrowRight);
+ animateAlphaTo(0, mArrowDown);
+ animateAlphaTo(0, mArrowLeft);
+ }
+
+ /**
+ * Show or hide the pip user actions.
+ */
+ public void showMenuButtons(boolean show) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenuButtons: %b", TAG, show);
+ }
+ animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+ }
+
+ private void showMenuFrame(boolean show) {
+ animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+ }
+
interface Listener {
+
void onBackPress();
+
+ void onEnterMoveMode();
+
+ /**
+ * Called when a button for exiting move mode was pressed.
+ *
+ * @return true if the event was handled or false if the key event should be handled by the
+ * next receiver.
+ */
+ boolean onExitMoveMode();
+
+ /**
+ * @return whether pip movement was handled.
+ */
+ boolean onPipMovement(int keycode);
+
void onCloseButtonClick();
+
void onFullscreenButtonClick();
+
+ void onToggleExpandedMode();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index dd7e29451ffc..4033f030b702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -28,13 +28,13 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.os.Handler;
-import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.Objects;
@@ -56,6 +56,10 @@ public class TvPipNotificationController {
"com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
private static final String ACTION_CLOSE_PIP =
"com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+ private static final String ACTION_MOVE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+ private static final String ACTION_TOGGLE_EXPANDED_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
private final Context mContext;
private final PackageManager mPackageManager;
@@ -98,7 +102,10 @@ public class TvPipNotificationController {
}
void setDelegate(Delegate delegate) {
- if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setDelegate(), delegate=%s", TAG, delegate);
+ }
if (mDelegate != null) {
throw new IllegalStateException(
"The delegate has already been set and should not change.");
@@ -219,6 +226,8 @@ public class TvPipNotificationController {
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(ACTION_CLOSE_PIP);
mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+ mIntentFilter.addAction(ACTION_MOVE_PIP);
+ mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
}
boolean mRegistered = false;
@@ -240,12 +249,19 @@ public class TvPipNotificationController {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "on(Broadcast)Receive(), action=" + action);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+ }
if (ACTION_SHOW_PIP_MENU.equals(action)) {
mDelegate.showPictureInPictureMenu();
} else if (ACTION_CLOSE_PIP.equals(action)) {
mDelegate.closePip();
+ } else if (ACTION_MOVE_PIP.equals(action)) {
+ mDelegate.enterPipMovementMenu();
+ } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
+ mDelegate.togglePipExpansion();
}
}
}
@@ -253,5 +269,7 @@ public class TvPipNotificationController {
interface Delegate {
void showPictureInPictureMenu();
void closePip();
+ void enterPipMovementMenu();
+ void togglePipExpansion();
}
}
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 551476dc9d54..5062cc436461 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
@@ -29,7 +29,6 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.ShellTaskOrganizer;
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.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
@@ -42,11 +41,11 @@ import com.android.wm.shell.transition.Transitions;
public class TvPipTransition extends PipTransitionController {
public TvPipTransition(PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm,
+ TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
Transitions transitions,
@NonNull ShellTaskOrganizer shellTaskOrganizer) {
- super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController,
+ super(pipBoundsState, pipMenuController, tvPipBoundsAlgorithm, pipAnimationController,
transitions, shellTaskOrganizer);
}
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 963a3dc70262..64017e176fc3 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,14 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_STARTING_WINDOW),
+ WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ "ShellBackPreview"),
+ WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
+ WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG,
+ false, Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
@@ -91,6 +99,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
private static class Consts {
private static final String TAG_WM_SHELL = "WindowManagerShell";
+ private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
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 338c944f7eec..c166178e9bbd 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
@@ -34,6 +34,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -41,6 +42,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;
@@ -128,6 +130,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
mTaskSplitBoundsMap.put(taskId1, splitBounds);
mTaskSplitBoundsMap.put(taskId2, splitBounds);
notifyRecentTasksChanged();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
+ taskId1, taskId2, splitBounds);
}
/**
@@ -141,6 +145,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
mTaskSplitBoundsMap.remove(taskId);
mTaskSplitBoundsMap.remove(pairedTaskId);
notifyRecentTasksChanged();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Remove split pair: %d, %d",
+ taskId, pairedTaskId);
}
}
@@ -182,6 +188,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
@VisibleForTesting
void notifyRecentTasksChanged() {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Notify recent tasks changed");
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).run();
}
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 3cfa541c1c86..9adf1961ebf5 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
@@ -42,11 +42,6 @@ interface ISplitScreen {
oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
/**
- * Hides the side-stage if it is currently visible.
- */
- oneway void setSideStageVisibility(boolean visible) = 3;
-
- /**
* Removes a task from the side stage.
*/
oneway void removeFromSideStage(int taskId) = 4;
@@ -89,9 +84,16 @@ interface ISplitScreen {
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Start a pair of intent and task using legacy transition system.
+ */
+ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
+ in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
+ int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter) = 12;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
@@ -100,5 +102,7 @@ interface ISplitScreen {
* @param appTargets apps that will be re-parented to display area
*/
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- in RemoteAnimationTarget[] appTargets) = 12;
+ in RemoteAnimationTarget[] appTargets) = 13;
+
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 082fe9205be8..ae5e075c4d3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -49,14 +48,10 @@ class MainStage extends StageTaskListener {
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
+ void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- // Moving the root task to top after the child tasks were re-parented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
if (includingTopTask) {
wct.reparentTasks(
null /* currentParent */,
@@ -85,9 +80,6 @@ class MainStage extends StageTaskListener {
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
CONTROLLED_ACTIVITY_TYPES,
- toTop)
- // We want this re-order to the bottom regardless since we are re-parenting
- // all its tasks.
- .reorder(rootToken, false /* onTop */);
+ toTop);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 122fc9f5f780..d55619f5e5ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -45,9 +45,6 @@ class SideStage extends StageTaskListener {
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
- // No matter if the root task is empty or not, moving the root to bottom because it no
- // longer preserves visible child task.
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index a91dfe1c13e2..448773ae9ea2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -76,12 +76,6 @@ public interface SplitScreen {
}
/**
- * Called when the keyguard occluded state changes.
- * @param occluded Indicates if the keyguard is now occluded.
- */
- void onKeyguardOccludedChanged(boolean occluded);
-
- /**
* Called when the visibility of the keyguard changes.
* @param showing Indicates if the keyguard is now visible.
*/
@@ -90,9 +84,6 @@ public interface SplitScreen {
/** Called when device waking up finished. */
void onFinishedWakingUp();
- /** Called when device going to sleep finished. */
- void onFinishedGoingToSleep();
-
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
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 4c77f6a7e00d..f20870ff0b2d 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
@@ -18,19 +18,23 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
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.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -58,6 +62,7 @@ import com.android.internal.logging.InstanceId;
import com.android.launcher3.icons.IconProvider;
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.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.RemoteCallable;
@@ -66,6 +71,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.recents.RecentTasksController;
@@ -124,6 +130,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
private final Transitions mTransitions;
@@ -141,7 +148,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ ShellExecutor mainExecutor, DisplayController displayController,
+ DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
@@ -151,6 +159,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mContext = context;
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
@@ -179,7 +188,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);
}
@@ -191,11 +200,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
- if (isSplitScreenVisible()) {
- int taskId = mStageCoordinator.getTaskId(splitPosition);
- return mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
+ return null;
}
- return null;
+
+ final int taskId = mStageCoordinator.getTaskId(splitPosition);
+ return mTaskOrganizer.getRunningTaskInfo(taskId);
}
public boolean isTaskInSplitScreen(int taskId) {
@@ -229,14 +239,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
- public void setSideStageVisibility(boolean visible) {
- mStageCoordinator.setSideStageVisibility(visible);
- }
-
public void enterSplitScreen(int taskId, boolean leftOrTop) {
enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
+ public void prepareEnterSplitScreen(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
+ mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition);
+ }
+
+ public void finishEnterSplitScreen(SurfaceControl.Transaction t) {
+ mStageCoordinator.finishEnterSplitScreen(t);
+ }
+
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
final int stagePosition =
@@ -248,10 +263,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
}
- public void onKeyguardOccludedChanged(boolean occluded) {
- mStageCoordinator.onKeyguardOccludedChanged(occluded);
- }
-
public void onKeyguardVisibilityChanged(boolean showing) {
mStageCoordinator.onKeyguardVisibilityChanged(showing);
}
@@ -260,10 +271,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onFinishedWakingUp();
}
- public void onFinishedGoingToSleep() {
- mStageCoordinator.onFinishedGoingToSleep();
- }
-
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
}
@@ -315,17 +322,33 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
- public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
- if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
+ @SplitPosition int position, @Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
- mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
- null /* remote */);
+
+ try {
+ options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
+ null /* wct */);
+
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the
+ // current top activity since it's going to be put into another side of the split. This
+ // prevents the current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) {
+ fillInIntent = new Intent();
+ }
+ fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+
+ intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
+ null /* requiredPermission */, options);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
}
- private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
@@ -336,17 +359,32 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
-
- if (apps != null) {
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
+ if (apps == null || apps.length == 0) {
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity =
+ pairedTaskInfo != null ? pairedTaskInfo.baseActivity : null;
+ final ComponentName intentActivity =
+ intent.getIntent() != null ? intent.getIntent().getComponent() : null;
+ if (pairedActivity != null && pairedActivity.equals(intentActivity)) {
+ // Switch split position if dragging the same activity to another side.
+ setSideStagePosition(SplitLayout.reversePosition(
+ mStageCoordinator.getSideStagePosition()));
}
+
+ // Do nothing when the animation was cancelled.
+ t.apply();
+ return;
}
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
t.apply();
+
if (finishedCallback != null) {
try {
finishedCallback.onAnimationFinished();
@@ -361,12 +399,22 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
+
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) {
+ fillInIntent = new Intent();
+ }
+ fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
- if (apps.length < 2) return null;
+ if (ENABLE_SHELL_TRANSITIONS || apps.length < 2) return null;
+ // TODO(b/206487881): Integrate this with shell transition.
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
if (mSplitTasksContainerLayer != null) {
// Remove the previous layer before recreating
@@ -487,13 +535,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void onKeyguardOccludedChanged(boolean occluded) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardOccludedChanged(occluded);
- });
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -534,13 +575,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
SplitScreenController.this.onFinishedWakingUp();
});
}
-
- @Override
- public void onFinishedGoingToSleep() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedGoingToSleep();
- });
- }
}
/**
@@ -607,14 +641,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void setSideStageVisibility(boolean visible) {
- executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
- (controller) -> {
- controller.setSideStageVisibility(visible);
- });
- }
-
- @Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
(controller) -> {
@@ -641,6 +667,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
+ int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController,
+ "startIntentAndTaskWithLegacyTransition", (controller) ->
+ controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
+ pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
+ sidePosition, splitRatio, adapter));
+ }
+
+ @Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio,
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 86e7b0e4cb7f..d30d0cc95f46 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
@@ -23,8 +23,13 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -39,8 +44,11 @@ import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -57,30 +65,29 @@ class SplitScreenTransitions {
private final Transitions mTransitions;
private final Runnable mOnFinish;
- IBinder mPendingDismiss = null;
+ DismissTransition mPendingDismiss = null;
IBinder mPendingEnter = null;
+ IBinder mPendingRecent = null;
private IBinder mAnimatingTransition = null;
- private OneShotRemoteHandler mRemoteHandler = null;
+ private OneShotRemoteHandler mPendingRemoteHandler = null;
+ private OneShotRemoteHandler mActiveRemoteHandler = null;
- private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
- if (wct != null || wctCB != null) {
- throw new UnsupportedOperationException("finish transactions not supported yet.");
- }
- onFinish();
- };
+ private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
/** Keeps track of currently running animations */
private final ArrayList<Animator> mAnimations = new ArrayList<>();
+ private final StageCoordinator mStageCoordinator;
private Transitions.TransitionFinishCallback mFinishCallback = null;
private SurfaceControl.Transaction mFinishTransaction;
SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
- @NonNull Runnable onFinishCallback) {
+ @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
mTransactionPool = pool;
mTransitions = transitions;
mOnFinish = onFinishCallback;
+ mStageCoordinator = stageCoordinator;
}
void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -90,10 +97,11 @@ class SplitScreenTransitions {
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
- mRemoteFinishCB);
- mRemoteHandler = null;
+ if (mPendingRemoteHandler != null) {
+ mPendingRemoteHandler.startAnimation(transition, info, startTransaction,
+ finishTransaction, mRemoteFinishCB);
+ mActiveRemoteHandler = mPendingRemoteHandler;
+ mPendingRemoteHandler = null;
return;
}
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
@@ -127,12 +135,6 @@ class SplitScreenTransitions {
}
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Dismissing split via snap which means the still-visible task has been
- // dragged to its end position at animation start so reflect that here.
- startBounds.offsetTo(change.getEndAbsBounds().left,
- change.getEndAbsBounds().top);
- }
final Rect endBounds = new Rect(change.getEndAbsBounds());
startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
@@ -144,10 +146,11 @@ class SplitScreenTransitions {
if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
|| sideRoot.equals(change.getContainer()))) {
- t.setWindowCrop(leash, change.getStartAbsBounds().width(),
- change.getStartAbsBounds().height());
+ t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+ t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
}
- boolean isOpening = isOpeningType(info.getType());
+ boolean isOpening = isOpeningTransition(info);
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
// fade in
startExampleAnimation(leash, true /* show */);
@@ -164,36 +167,69 @@ class SplitScreenTransitions {
}
}
t.apply();
- onFinish();
+ onFinish(null /* wct */, null /* wctCB */);
}
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
@NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
@NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mRemoteHandler = new OneShotRemoteHandler(
+ mPendingRemoteHandler = new OneShotRemoteHandler(
mTransitions.getMainExecutor(), remoteTransition);
+ mPendingRemoteHandler.setTransition(transition);
}
- final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- mPendingEnter = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.setTransition(transition);
+ return transition;
+ }
+
+ /** Starts a transition to dismiss split. */
+ IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
+ Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
+ @SplitScreenController.ExitReason int reason) {
+ final int type = reason == EXIT_REASON_DRAG_DIVIDER
+ ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
+ if (transition == null) {
+ transition = mTransitions.startTransition(type, wct, handler);
}
+ mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss due to %s. toTop=%s",
+ exitReasonToString(reason), stageTypeToString(dismissTop));
return transition;
}
- /** Starts a transition for dismissing split after dragging the divider to a screen edge */
- IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
- @NonNull Transitions.TransitionHandler handler) {
- final IBinder transition = mTransitions.startTransition(
- TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
- mPendingDismiss = transition;
+ IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
+ Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) {
+ if (transition == null) {
+ transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler);
+ }
+ mPendingRecent = transition;
+
+ 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");
return transition;
}
- void onFinish() {
+ void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+ IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
+ if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) {
+ mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ }
+ }
+
+ void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
mOnFinish.run();
if (mFinishTransaction != null) {
@@ -201,14 +237,25 @@ class SplitScreenTransitions {
mTransactionPool.release(mFinishTransaction);
mFinishTransaction = null;
}
- mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- mFinishCallback = null;
+ if (mFinishCallback != null) {
+ mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
+ mFinishCallback = null;
+ }
if (mAnimatingTransition == mPendingEnter) {
mPendingEnter = null;
}
- if (mAnimatingTransition == mPendingDismiss) {
+ if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
mPendingDismiss = null;
}
+ if (mAnimatingTransition == mPendingRecent) {
+ // If the wct is not null while finishing recent transition, it indicates it's not
+ // dismissing split and thus need to reorder split task so they can be on top again.
+ final boolean dismissSplit = wct == null;
+ mStageCoordinator.finishRecentAnimation(dismissSplit);
+ mPendingRecent = null;
+ }
+ mPendingRemoteHandler = null;
+ mActiveRemoteHandler = null;
mAnimatingTransition = null;
}
@@ -230,7 +277,7 @@ class SplitScreenTransitions {
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
- onFinish();
+ onFinish(null /* wct */, null /* wctCB */);
});
};
va.addListener(new Animator.AnimatorListener() {
@@ -278,7 +325,7 @@ class SplitScreenTransitions {
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
- onFinish();
+ onFinish(null /* wct */, null /* wctCB */);
});
};
va.addListener(new AnimatorListenerAdapter() {
@@ -295,4 +342,26 @@ class SplitScreenTransitions {
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
}
+
+ private boolean isOpeningTransition(TransitionInfo info) {
+ return Transitions.isOpeningType(info.getType())
+ || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE
+ || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+ }
+
+ /** Bundled information of dismiss transition. */
+ static class DismissTransition {
+ IBinder mTransition;
+
+ int mReason;
+
+ @SplitScreen.StageType
+ int mDismissTop;
+
+ DismissTransition(IBinder transition, int reason, int dismissTop) {
+ this.mTransition = transition;
+ 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 38c1aff0a62c..b10d50da10bf 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
@@ -18,13 +18,17 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
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.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
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;
@@ -32,23 +36,24 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -58,6 +63,7 @@ import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
@@ -72,7 +78,6 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -83,10 +88,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
-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.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
@@ -115,18 +121,16 @@ import javax.inject.Provider;
* - The {@link MainStage} should only have children if the coordinator is active.
* - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
* and {@link SideStage} are visible.
- * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * - Both stages are put under a single-top root task.
* This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
* {@link #onStageHasChildrenChanged(StageListenerImpl).}
*/
class StageCoordinator implements SplitLayout.SplitLayoutHandler,
- RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+ DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
+ ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
- /** internal value for mDismissTop that represents no dismiss */
- private static final int NO_DISMISS = -2;
-
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
@@ -135,6 +139,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
private final StageTaskUnfoldController mSideUnfoldController;
+ private final DisplayLayout mDisplayLayout;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -142,47 +147,40 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private SplitLayout mSplitLayout;
private boolean mDividerVisible;
private final SyncTransactionQueue mSyncQueue;
- private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellTaskOrganizer mTaskOrganizer;
- private DisplayAreaInfo mDisplayAreaInfo;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
+ private final TransactionPool mTransactionPool;
private final SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private final Optional<RecentTasksController> mRecentTasks;
+
+ /**
+ * A single-top root task which the split divider attached to.
+ */
+ @VisibleForTesting
+ ActivityManager.RunningTaskInfo mRootTaskInfo;
+
+ private SurfaceControl mRootTaskLeash;
+
// Tracks whether we should update the recent tasks. Only allow this to happen in between enter
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mKeyguardOccluded;
- private boolean mDeviceSleep;
private boolean mIsDividerRemoteAnimating;
- @StageType
- private int mDismissTop = NO_DISMISS;
-
/** The target stage to dismiss to when unlock after folded. */
@StageType
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- private final Runnable mOnTransitionAnimationComplete = () -> {
- // If still playing, let it finish.
- if (!isSplitScreenVisible()) {
- // Update divider state after animation so that it is still around and positioned
- // properly for the animation itself.
- setDividerVisibility(false);
- mSplitLayout.resetDividerPosition();
- }
- mDismissTop = NO_DISMISS;
- };
-
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@Override
public void attachToParentSurface(SurfaceControl.Builder b) {
- mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ b.setParent(mRootTaskLeash);
}
@Override
@@ -192,22 +190,21 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
};
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
- IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
+ IconProvider iconProvider, Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
mLogger = logger;
mRecentTasks = recentTasks;
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
mMainStage = new MainStage(
mContext,
@@ -227,22 +224,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSurfaceSession,
iconProvider,
mSideUnfoldController);
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
- mRootTDAOrganizer.registerListener(displayId, this);
+ mTransactionPool = transactionPool;
final DeviceStateManager deviceStateManager =
mContext.getSystemService(DeviceStateManager.class);
deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
+ this::onTransitionAnimationComplete, this);
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
}
@VisibleForTesting
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
+ DisplayController displayController, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger,
@@ -251,20 +251,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
mMainStage = mainStage;
mSideStage = sideStage;
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
- mRootTDAOrganizer.registerListener(displayId, this);
+ mTransactionPool = transactionPool;
mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
+ this::onTransitionAnimationComplete, this);
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mLogger = logger;
mRecentTasks = recentTasks;
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
}
@@ -316,7 +318,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
}
- mTaskOrganizer.applyTransaction(wct);
+
+ if (ENABLE_SHELL_TRANSITIONS) {
+ prepareEnterSplitScreen(wct);
+ mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE,
+ wct, null, this);
+ } else {
+ mTaskOrganizer.applyTransaction(wct);
+ }
return true;
}
@@ -343,12 +352,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
+ mSplitLayout.setDivideRatio(splitRatio);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
- mSideStage.setBounds(getSideStageBounds(), wct);
+ mMainStage.activate(wct, false /* reparent */);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
- mSplitLayout.setDivideRatio(splitRatio);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
@@ -365,8 +375,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
- // Ensure divider is invisible before transition.
- setDividerVisibility(false /* visible */);
+ startWithLegacyTransition(mainTaskId, sideTaskId, null /* pendingIntent */,
+ null /* fillInIntent */, mainOptions, sideOptions, sidePosition, splitRatio,
+ adapter);
+ }
+
+ /** Start an intent and a task ordered by {@code intentFirst}. */
+ void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
+ int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ startWithLegacyTransition(taskId, INVALID_TASK_ID, pendingIntent, fillInIntent,
+ mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+ }
+
+ private void startWithLegacyTransition(int mainTaskId, int sideTaskId,
+ @Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
// Set false to avoid record new bounds with old task still on top;
@@ -398,9 +423,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onAnimationFinished() throws RemoteException {
mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
- setDividerVisibility(true /* visible */);
mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
finishedCallback.onAnimationFinished();
}
};
@@ -423,9 +447,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onAnimationCancelled() {
mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
- setDividerVisibility(true /* visible */);
mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
try {
adapter.getRunner().onAnimationCancelled();
} catch (RemoteException e) {
@@ -448,36 +471,30 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(sidePosition, wct);
mSplitLayout.setDivideRatio(splitRatio);
- if (mMainStage.isActive()) {
- mMainStage.moveToTop(getMainStageBounds(), wct);
- } else {
+ 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(getMainStageBounds(), wct, false /* reparent */);
+ mMainStage.activate(wct, false /* reparent */);
}
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
+ if (pendingIntent != null && fillInIntent != null) {
+ wct.startTask(mainTaskId, mainOptions);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+ }
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
- }
-
- public void startIntent(PendingIntent intent, Intent fillInIntent,
- @StageType int stage, @SplitPosition int position,
- @androidx.annotation.Nullable Bundle options,
- @Nullable RemoteTransition remoteTransition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(stage, position, options, wct);
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
}
/**
@@ -508,8 +525,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
}
} else {
- // Exit split-screen and launch fullscreen since stage wasn't specified.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ Slog.w(TAG,
+ "No stage type nor split position specified to resolve start stage");
}
break;
}
@@ -585,37 +602,54 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- void setSideStageVisibility(boolean visible) {
- if (mSideStageListener.mVisible == visible) return;
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStage.setVisibility(visible, wct);
- mTaskOrganizer.applyTransaction(wct);
- }
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!mMainStage.isActive()) {
+ return;
+ }
- void onKeyguardOccludedChanged(boolean occluded) {
- // Do not exit split directly, because it needs to wait for task info update to determine
- // which task should remain on top after split dismissed.
- mKeyguardOccluded = occluded;
- }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Update divider visibility so it won't float on top of keyguard.
+ setDividerVisibility(!showing, null /* transaction */);
+ }
- void onKeyguardVisibilityChanged(boolean showing) {
- if (!showing && mMainStage.isActive()
- && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
+ if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
+ }
}
}
void onFinishedWakingUp() {
- if (mMainStage.isActive()) {
- exitSplitScreenIfKeyguardOccluded();
+ if (!mMainStage.isActive()) {
+ return;
}
- mDeviceSleep = false;
- }
- void onFinishedGoingToSleep() {
- mDeviceSleep = true;
+ // Check if there's only one stage visible while keyguard occluded.
+ final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
+ final boolean oneStageVisible =
+ mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
+ if (oneStageVisible) {
+ // Dismiss split because there's show-when-locked activity showing on top of keyguard.
+ // Also make sure the task contains show-when-locked activity remains on top after split
+ // dismissed.
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ } else {
+ final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(dismissTop, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+ }
}
void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -639,28 +673,18 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
- private void exitSplitScreen(StageTaskListener childrenToTop, @ExitReason int exitReason) {
+ private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
+ @ExitReason int exitReason) {
if (!mMainStage.isActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
- private void exitSplitScreenIfKeyguardOccluded() {
- final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean oneStageVisible = mainStageVisible ^ mSideStageListener.mVisible;
- if (mDeviceSleep && mKeyguardOccluded && oneStageVisible) {
- // Only the stages include show-when-locked activity is visible while keyguard occluded.
- // Dismiss split because there's show-when-locked activity showing on top of keyguard.
- // Also make sure the task contains show-when-locked activity remains on top after split
- // dismissed.
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
- }
-
- private void applyExitSplitScreen(StageTaskListener childrenToTop,
+ private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
@@ -675,16 +699,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// we want the tasks to be put to bottom instead of top, otherwise it will end up
// a fullscreen plus a pinned task instead of pinned only at the end of the transition.
final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP;
- mSideStage.removeAllTasks(wct, !fromEnteringPip && childrenToTop == mSideStage);
- mMainStage.deactivate(wct, !fromEnteringPip && childrenToTop == mMainStage);
+ mSideStage.removeAllTasks(wct, !fromEnteringPip && mSideStage == childrenToTop);
+ mMainStage.deactivate(wct, !fromEnteringPip && mMainStage == childrenToTop);
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> t
.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null));
// Hide divider and reset its position.
- setDividerVisibility(false);
mSplitLayout.resetDividerPosition();
+ mSplitLayout.release();
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
// Log the exit
@@ -708,6 +733,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case EXIT_REASON_APP_FINISHED:
// One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
+ // One of the apps occludes lock screen.
+ case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
+ // User has unlocked the device after folded
+ case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
return false;
@@ -719,12 +748,49 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
* to be used when exiting split might be bundled with other window operations.
*/
- void prepareExitSplitScreen(@StageType int stageToTop,
+ private void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
+ if (!mMainStage.isActive()) return;
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
}
+ private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
+ prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED);
+ }
+
+ /**
+ * Prepare transaction to active split screen. If there's a task indicated, the task will be put
+ * into side stage.
+ */
+ void prepareEnterSplitScreen(WindowContainerTransaction wct,
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ if (mMainStage.isActive()) return;
+
+ if (taskInfo != null) {
+ setSideStagePosition(startPosition, wct);
+ mSideStage.addTask(taskInfo, wct);
+ }
+ mMainStage.activate(wct, true /* includingTopTask */);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ }
+
+ void finishEnterSplitScreen(SurfaceControl.Transaction t) {
+ mSplitLayout.init();
+ setDividerVisibility(true, t);
+ updateSurfaceBounds(mSplitLayout, t);
+ setSplitsVisible(true);
+ mShouldUpdateRecents = true;
+ updateRecentTasksSplitPair();
+ if (!mLogger.hasStartedSession()) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
outTopOrLeftBounds.set(mSplitLayout.getBounds1());
outBottomOrRightBounds.set(mSplitLayout.getBounds2());
@@ -841,91 +907,151 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ updateUnfoldBounds();
}
}
- private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
- if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
- true /* moveTogether */);
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- mTaskOrganizer.applyTransaction(wct);
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo != null || taskInfo.hasParentTask()) {
+ throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
+ }
+
+ mRootTaskInfo = taskInfo;
+ mRootTaskLeash = leash;
+
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mRootTaskInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer, false /* applyDismissingParallax */);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
}
+
+ onRootTaskAppeared();
}
- private void onStageRootTaskVanished(StageListenerImpl stageListener) {
- if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- // Deactivate the main stage if it no longer has a root task.
- mMainStage.deactivate(wct);
- mTaskOrganizer.applyTransaction(wct);
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
+ throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
+ }
+
+ mRootTaskInfo = taskInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
+ && mMainStage.isActive()) {
+ // TODO(b/204925795): With Shell transition, We are handling split bounds rotation at
+ // onRotateDisplay. But still need to handle unfold case.
+ if (ENABLE_SHELL_TRANSITIONS) {
+ updateUnfoldBounds();
+ return;
+ }
+ mSplitLayout.update(null /* t */);
+ onLayoutSizeChanged(mSplitLayout);
}
}
- private void setDividerVisibility(boolean visible) {
- if (mIsDividerRemoteAnimating || mDividerVisible == visible) return;
- mDividerVisible = visible;
- if (visible) {
- mSplitLayout.init();
- updateUnfoldBounds();
- } else {
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mRootTaskInfo == null) {
+ throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo);
+ }
+
+ onRootTaskVanished();
+
+ if (mSplitLayout != null) {
mSplitLayout.release();
+ mSplitLayout = null;
}
- sendSplitVisibilityChanged();
+
+ mRootTaskInfo = null;
+ }
+
+
+ @VisibleForTesting
+ void onRootTaskAppeared() {
+ // Wait unit all root tasks appeared.
+ if (mRootTaskInfo == null
+ || !mMainStageListener.mHasRootTask
+ || !mSideStageListener.mHasRootTask) {
+ return;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+ wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ true /* moveTogether */);
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ private void onRootTaskVanished() {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mRootTaskInfo != null) {
+ wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token);
+ }
+ applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
+ mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
final boolean sideStageVisible = mSideStageListener.mVisible;
final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean bothStageVisible = sideStageVisible && mainStageVisible;
- final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
- final boolean sameVisibility = sideStageVisible == mainStageVisible;
- // Only add or remove divider when both visible or both invisible to avoid sometimes we only
- // got one stage visibility changed for a moment and it will cause flicker.
- if (sameVisibility) {
- setDividerVisibility(bothStageVisible);
+
+ // Wait for both stages having the same visibility to prevent causing flicker.
+ if (mainStageVisible != sideStageVisible) {
+ return;
}
- if (bothStageInvisible) {
+ if (!mainStageVisible) {
+ // Both stages are not visible, check if it needs to dismiss split screen.
if (mExitSplitScreenOnHide
- // Don't dismiss staged split when both stages are not visible due to sleeping
- // display, like the cases keyguard showing or screen off.
+ // Don't dismiss split screen when both stages are not visible due to sleeping
+ // display.
|| (!mMainStage.mRootTaskInfo.isSleeping
&& !mSideStage.mRootTaskInfo.isSleeping)) {
- // Don't dismiss staged split when both stages are not visible due to sleeping display,
- // like the cases keyguard showing or screen off.
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
}
}
- exitSplitScreenIfKeyguardOccluded();
mSyncQueue.runInSync(t -> {
- // Same above, we only set root tasks and divider leash visibility when both stage
- // change to visible or invisible to avoid flicker.
- if (sameVisibility) {
- t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
- .setVisibility(mMainStage.mRootLeash, bothStageVisible);
- applyDividerVisibility(t);
- }
+ t.setVisibility(mSideStage.mRootLeash, sideStageVisible)
+ .setVisibility(mMainStage.mRootLeash, mainStageVisible);
+ setDividerVisibility(mainStageVisible, t);
});
}
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
- if (mIsDividerRemoteAnimating) return;
+ private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
+ mDividerVisible = visible;
+ sendSplitVisibilityChanged();
+ if (t != null) {
+ applyDividerVisibility(t);
+ } else {
+ mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
+ }
+ }
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (dividerLeash == null) return;
+ if (mIsDividerRemoteAnimating || dividerLeash == null) return;
if (mDividerVisible) {
- t.show(dividerLeash)
- .setAlpha(dividerLeash, 1)
- .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
- .setPosition(dividerLeash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top);
+ t.show(dividerLeash);
+ t.setAlpha(dividerLeash, 1);
+ t.setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setPosition(dividerLeash,
+ mSplitLayout.getRefDividerBounds().left,
+ mSplitLayout.getRefDividerBounds().top);
} else {
t.hide(dividerLeash);
}
@@ -942,13 +1068,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Exit to side stage if main stage no longer has children.
exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
}
- } else if (isSideStage) {
+ } else if (isSideStage && !mMainStage.isActive()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t);
+ setDividerVisibility(true, t);
+ });
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -963,23 +1091,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- @VisibleForTesting
- IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
- return mSplitTransitions.startSnapToDismiss(wct, this);
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight) {
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
- if (ENABLE_SHELL_TRANSITIONS) {
- onSnappedToDismissTransition(mainStageToTop);
+
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER);
return;
}
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER);
+
+ final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(dismissTop, wct);
+ mSplitTransitions.startDismissTransition(
+ null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
}
@Override
@@ -1012,19 +1139,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
updateSurfaceBounds(layout, t);
- mMainStage.onResized(getMainStageBounds(), t);
- mSideStage.onResized(getSideStageBounds(), t);
+ mMainStage.onResized(t);
+ mSideStage.onResized(t);
});
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
private void updateUnfoldBounds() {
if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(),
+ isLandscape());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(),
+ isLandscape());
}
}
+ private boolean isLandscape() {
+ return mSplitLayout.isLandscape();
+ }
+
/**
* Populates `wct` with operations that match the split windows to the current layout.
* To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
@@ -1052,9 +1185,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return SPLIT_POSITION_UNDEFINED;
}
- if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ if (mMainStage.containsToken(token)) {
return getMainStagePosition();
- } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ } else if (mSideStage.containsToken(token)) {
return getSideStagePosition();
}
@@ -1074,34 +1207,31 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout == null) {
- mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
- mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
- mDisplayImeController, mTaskOrganizer, false /* applyDismissingParallax */);
- mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.init();
- mSideUnfoldController.init();
- }
+ public void onDisplayAdded(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
}
+ mDisplayController.addDisplayChangingController(this::onRotateDisplay);
}
@Override
- public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
- throw new IllegalStateException("Well that was unexpected...");
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
}
- @Override
- public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
- && mMainStage.isActive()) {
- onLayoutSizeChanged(mSplitLayout);
- }
+ private void onRotateDisplay(int displayId, int fromRotation, int toRotation,
+ WindowContainerTransaction wct) {
+ if (!mMainStage.isActive()) return;
+ // Only do this when shell transition
+ if (!ENABLE_SHELL_TRANSITIONS) return;
+
+ mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+ mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets());
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
}
private void onFoldedStateChanged(boolean folded) {
@@ -1152,14 +1282,35 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
- // still want to monitor everything while in split-screen, so return non-null.
- return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ if (mMainStage.isActive()) {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ request.getDisplayChange();
+ if (request.getType() == TRANSIT_CHANGE && displayChange != null
+ && displayChange.getStartRotation() != displayChange.getEndRotation()) {
+ mSplitLayout.setFreezeDividerWindow(true);
+ }
+ // Still want to monitor everything while in split-screen, so return non-null.
+ return new WindowContainerTransaction();
+ } else {
+ return null;
+ }
+ } else if (triggerTask.displayId != mDisplayId) {
+ // Skip handling task on the other display.
+ return null;
}
WindowContainerTransaction out = null;
final @WindowManager.TransitionType int type = request.getType();
- if (isSplitScreenVisible()) {
- // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ final boolean isOpening = isOpeningType(type);
+ final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+
+ if (isOpening && inFullscreen) {
+ // One task is opening into fullscreen mode, remove the corresponding split record.
+ mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
+ }
+
+ if (mMainStage.isActive()) {
+ // Try to handle everything while in split-screen, so return a WCT even if it's empty.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
@@ -1167,53 +1318,85 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
out = new WindowContainerTransaction();
final StageTaskListener stage = getStageOfTask(triggerTask);
if (stage != null) {
- // dismiss split if the last task in one of the stages is going away
+ // Dismiss split if the last task in one of the stages is going away
if (isClosingType(type) && stage.getChildCount() == 1) {
// The top should be the opposite side that is closing:
- mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
- ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
+ : STAGE_TYPE_MAIN;
+ prepareExitSplitScreen(dismissTop, out);
+ mSplitTransitions.startDismissTransition(transition, out, this, dismissTop,
+ EXIT_REASON_APP_FINISHED);
}
- } else {
- if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
- // Going home so dismiss both.
- mDismissTop = STAGE_TYPE_UNDEFINED;
+ } else if (isOpening && inFullscreen) {
+ final int activityType = triggerTask.getActivityType();
+ if (activityType == ACTIVITY_TYPE_ASSISTANT) {
+ // We don't want assistant panel to dismiss split screen, so do nothing.
+ } else if (activityType == ACTIVITY_TYPE_HOME
+ || activityType == ACTIVITY_TYPE_RECENTS) {
+ // Enter overview panel, so start recent transition.
+ mSplitTransitions.startRecentTransition(transition, out, this,
+ request.getRemoteTransition());
+ } else {
+ // Occluded by the other fullscreen task, so dismiss both.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
+ mSplitTransitions.startDismissTransition(transition, out, this,
+ STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
}
}
- if (mDismissTop != NO_DISMISS) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Dismiss from request. toTop=%s",
- stageTypeToString(mDismissTop));
- prepareExitSplitScreen(mDismissTop, out);
- mSplitTransitions.mPendingDismiss = transition;
- }
} else {
- // Not in split mode, so look for an open into a split stage just so we can whine and
- // complain about how this isn't a supported operation.
- if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
- if (getStageOfTask(triggerTask) != null) {
- throw new IllegalStateException("Entering split implicitly with only one task"
- + " isn't supported.");
- }
+ if (isOpening && getStageOfTask(triggerTask) != null) {
+ // One task is appearing into split, prepare to enter split screen.
+ out = new WindowContainerTransaction();
+ prepareEnterSplitScreen(out);
+ mSplitTransitions.mPendingEnter = transition;
}
}
return out;
}
@Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ Transitions.TransitionFinishCallback finishCallback) {
+ mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder transition) {
+ // Once the pending enter transition got merged, make sure to bring divider bar visible and
+ // clear the pending transition from cache to prevent mess-up the following state.
+ if (transition == mSplitTransitions.mPendingEnter) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ finishEnterSplitScreen(t);
+ mSplitTransitions.mPendingEnter = null;
+ t.apply();
+ mTransactionPool.release(t);
+ }
+ }
+
+ @Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mSplitTransitions.mPendingDismiss
- && transition != mSplitTransitions.mPendingEnter) {
+ if (transition != mSplitTransitions.mPendingEnter
+ && transition != mSplitTransitions.mPendingRecent
+ && (mSplitTransitions.mPendingDismiss == null
+ || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
// Not entering or exiting, so just do some house-keeping and validation.
// If we're not in split-mode, just abort so something else can handle it.
- if (!isSplitScreenVisible()) return false;
+ if (!mMainStage.isActive()) return false;
+ mSplitLayout.setFreezeDividerWindow(false);
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
+ if (change.getMode() == TRANSIT_CHANGE
+ && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+ mSplitLayout.update(startTransaction);
+ }
+
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null || !taskInfo.hasParentTask()) continue;
final StageTaskListener stage = getStageOfTask(taskInfo);
@@ -1249,8 +1432,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean shouldAnimate = true;
if (mSplitTransitions.mPendingEnter == transition) {
shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingDismiss == transition) {
- shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingRecent == transition) {
+ shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss != null
+ && mSplitTransitions.mPendingDismiss.mTransition == transition) {
+ shouldAnimate = startPendingDismissAnimation(
+ mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
}
if (!shouldAnimate) return false;
@@ -1259,63 +1446,74 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
+ void onTransitionAnimationComplete() {
+ // If still playing, let it finish.
+ if (!mMainStage.isActive()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ mSplitLayout.release();
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ }
+ }
+
private boolean startPendingEnterAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
- // First, verify that we actually have opened 2 apps in split.
- TransitionInfo.Change mainChild = null;
- TransitionInfo.Change sideChild = null;
- for (int iC = 0; iC < info.getChanges().size(); ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
- final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
- if (stageType == STAGE_TYPE_MAIN) {
- mainChild = change;
- } else if (stageType == STAGE_TYPE_SIDE) {
- sideChild = change;
- }
+ // First, verify that we actually have opened apps in both splits.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
}
+ }
+
+ // TODO: fallback logic. Probably start a new transition to exit split before applying
+ // anything here. Ideally consolidate with transition-merging.
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
+ if (mainChild == null && sideChild == null) {
+ throw new IllegalStateException("Launched a task in split, but didn't receive any"
+ + " task in transition.");
+ }
+ } else {
if (mainChild == null || sideChild == null) {
throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
- // TODO: fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
}
+ }
- // Update local states (before animating).
- setDividerVisibility(true);
- setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
- null /* wct */);
- setSplitsVisible(true);
-
- addDividerBarToTransition(info, t, true /* show */);
-
- // Make some noise if things aren't totally expected. These states shouldn't effect
- // transitions locally, but remotes (like Launcher) may get confused if they were
- // depending on listener callbacks. This can happen because task-organizer callbacks
- // aren't serialized with transition callbacks.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
- + " to have been called with " + mainChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
- + " to have been called with " + sideChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- return true;
- } else {
- // TODO: other entry method animations
- throw new RuntimeException("Unsupported split-entry");
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
}
+
+ finishEnterSplitScreen(t);
+ addDividerBarToTransition(info, t, true /* show */);
+ return true;
}
- private boolean startPendingDismissAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ private boolean startPendingDismissAnimation(
+ @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction finishT) {
// Make some noise if things aren't totally expected. These states shouldn't effect
// transitions locally, but remotes (like Launcher) may get confused if they were
// depending on listener callbacks. This can happen because task-organizer callbacks
@@ -1343,34 +1541,80 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ "] before startAnimation().");
}
+ mRecentTasks.ifPresent(recentTasks -> {
+ // Notify recents if we are exiting in a way that breaks the pair, and disable further
+ // updates to splits in the recents until we enter split again
+ if (shouldBreakPairedTaskInRecents(dismissTransition.mReason) && mShouldUpdateRecents) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ recentTasks.removeSplitPair(taskInfo.taskId);
+ }
+ }
+ }
+ });
+ mShouldUpdateRecents = false;
+
// Update local states.
setSplitsVisible(false);
// Wait until after animation to update divider
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
- }
-
- if (mDismissTop == STAGE_TYPE_UNDEFINED) {
- // Going home (dismissing both splits)
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
+ logExit(dismissTransition.mReason);
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
- setDividerVisibility(false);
+ setDividerVisibility(false, t);
+ mSplitLayout.release();
mSplitTransitions.mPendingDismiss = null;
return false;
+ } else {
+ logExitToStage(dismissTransition.mReason,
+ dismissTransition.mDismissTop == STAGE_TYPE_MAIN);
}
addDividerBarToTransition(info, t, false /* show */);
// We're dismissing split by moving the other one to fullscreen.
// Since we don't have any animations for this yet, just use the internal example
// animations.
+
+ // Hide divider and dim layer on transition finished.
+ setDividerVisibility(false, finishT);
+ finishT.hide(mMainStage.mDimLayer);
+ finishT.hide(mSideStage.mDimLayer);
+ return true;
+ }
+
+ private boolean startPendingRecentAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ setDividerVisibility(false, t);
return true;
}
+ void finishRecentAnimation(boolean dismissSplit) {
+ // Exclude the case that the split screen has been dismissed already.
+ if (!mMainStage.isActive()) {
+ // The latest split dismissing transition might be a no-op transition and thus won't
+ // callback startAnimation, update split visibility here to cover this kind of no-op
+ // transition case.
+ setSplitsVisible(false);
+ return;
+ }
+
+ if (dismissSplit) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ STAGE_TYPE_UNDEFINED, EXIT_REASON_RETURN_HOME);
+ } else {
+ setDividerVisibility(true, null /* t */);
+ }
+ }
+
private void addDividerBarToTransition(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
@@ -1386,7 +1630,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
if (show) {
t.setAlpha(leash, 1.f);
- t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setLayer(leash, Integer.MAX_VALUE);
t.setPosition(leash, bounds.left, bounds.top);
t.show(leash);
}
@@ -1469,7 +1713,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onRootTaskAppeared() {
mHasRootTask = true;
- StageCoordinator.this.onStageRootTaskAppeared(this);
+ StageCoordinator.this.onRootTaskAppeared();
}
@Override
@@ -1499,13 +1743,21 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onRootTaskVanished() {
reset();
- StageCoordinator.this.onStageRootTaskVanished(this);
+ StageCoordinator.this.onRootTaskVanished();
}
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
- StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct,
+ StageCoordinator.this, STAGE_TYPE_UNDEFINED,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 04e20db369ef..9fd5d2003873 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
@@ -39,7 +39,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import com.android.internal.R;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
@@ -53,8 +52,8 @@ import java.io.PrintWriter;
* Base class that handle common task org. related for split-screen stages.
* Note that this class and its sub-class do not directly perform hierarchy operations.
* They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
+ * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized
+ * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers.
*
* @see StageCoordinator
*/
@@ -108,12 +107,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
mStageTaskUnfoldController = stageTaskUnfoldController;
-
- // No need to create root task if the device is using legacy split screen.
- // TODO(b/199236198): Remove this check after totally deprecated legacy split.
- if (!context.getResources().getBoolean(R.bool.config_useLegacySplit)) {
- taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
- }
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
int getChildCount() {
@@ -124,6 +118,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return mChildrenTaskInfo.contains(taskId);
}
+ boolean containsToken(WindowContainerToken token) {
+ if (token.equals(mRootTaskInfo.token)) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (token.equals(mChildrenTaskInfo.valueAt(i).token)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Returns the top visible child task's id.
*/
@@ -173,7 +181,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
mSplitDecorManager = new SplitDecorManager(
@@ -280,10 +288,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
if (mRootTaskInfo.taskId == taskId) {
- b.setParent(mRootLeash);
+ return mRootLeash;
} else if (mChildrenLeashes.contains(taskId)) {
- b.setParent(mChildrenLeashes.get(taskId));
+ return mChildrenLeashes.get(taskId);
} else {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
@@ -295,23 +313,19 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
- void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+ void onResized(SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
- mSplitDecorManager.onResized(newBounds, t);
+ mSplitDecorManager.onResized(t);
}
}
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
- wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
- }
-
- void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
- }
+ // Clear overridden bounds and windowing mode to make sure the child task can inherit
+ // windowing mode and bounds from split root.
+ wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
+ .setBounds(task.token, null);
- void setBounds(Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds);
+ wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
@@ -329,10 +343,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
- void setVisibility(boolean visible, WindowContainerTransaction wct) {
- wct.reorder(mRootTaskInfo.token, visible /* onTop */);
- }
-
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
@StageType int stage) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
index 4849163e96fd..59eecb5db136 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
@@ -18,11 +18,15 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.InsetsSource;
@@ -33,6 +37,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
@@ -166,12 +171,13 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
* Called when split screen stage bounds changed
* @param bounds new bounds for this stage
*/
- public void onLayoutChanged(Rect bounds) {
+ public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition,
+ boolean isLandscape) {
mStageBounds.set(bounds);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
+ context.update(splitPosition, isLandscape);
}
}
@@ -200,20 +206,27 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
+ private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
+ private boolean mIsLandscape = false;
+
private AnimationContext(SurfaceControl leash) {
this.mLeash = leash;
update();
}
+ private void update(@SplitPosition int splitPosition, boolean isLandscape) {
+ this.mSplitPosition = splitPosition;
+ this.mIsLandscape = isLandscape;
+ update();
+ }
+
private void update() {
mStartCropRect.set(mStageBounds);
- if (mTaskbarInsetsSource != null) {
+ boolean taskbarExpanded = isTaskbarExpanded();
+ if (taskbarExpanded) {
// Only insets the cropping window with taskbar when taskbar is expanded
- if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(mTaskbarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
+ mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
}
// Offset to surface coordinates as layout bounds are in screen coordinates
@@ -223,7 +236,46 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
- mStartCropRect.inset(margin, margin, margin, margin);
+
+ // Sides adjacent to split bar or task bar are not be animated.
+ Insets margins;
+ if (mIsLandscape) { // Left and right splits.
+ margins = getLandscapeMargins(margin, taskbarExpanded);
+ } else { // Top and bottom splits.
+ margins = getPortraitMargins(margin, taskbarExpanded);
+ }
+ mStartCropRect.inset(margins);
+ }
+
+ private Insets getLandscapeMargins(int margin, boolean taskbarExpanded) {
+ int left = margin;
+ int right = margin;
+ int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
+ if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ right = 0; // Divider margin.
+ } else {
+ left = 0; // Divider margin.
+ }
+ return Insets.of(left, /* top= */ margin, right, bottom);
+ }
+
+ private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
+ int bottom = margin;
+ int top = margin;
+ if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ bottom = 0; // Divider margin.
+ } else { // Bottom split.
+ top = 0; // Divider margin.
+ if (taskbarExpanded) {
+ bottom = 0; // Taskbar margin.
+ }
+ }
+ return Insets.of(/* left= */ margin, top, /* right= */ margin, bottom);
+ }
+
+ private boolean isTaskbarExpanded() {
+ return mTaskbarInsetsSource != null
+ && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
index af9a5aa501e8..018365420177 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -127,12 +127,6 @@ class SplitScreenTransitions {
}
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Dismissing split via snap which means the still-visible task has been
- // dragged to its end position at animation start so reflect that here.
- startBounds.offsetTo(change.getEndAbsBounds().left,
- change.getEndAbsBounds().top);
- }
final Rect endBounds = new Rect(change.getEndAbsBounds());
startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
index a17942ff7cff..6ef20e37d5bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -23,7 +23,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
@@ -722,7 +721,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mDividerVisible) {
t.show(dividerLeash)
- .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setLayer(dividerLeash, Integer.MAX_VALUE)
.setPosition(dividerLeash,
mSplitLayout.getDividerBounds().left,
mSplitLayout.getDividerBounds().top);
@@ -738,7 +737,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (mDividerVisible) {
- t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ t.show(outlineLeash).setLayer(outlineLeash, Integer.MAX_VALUE);
} else {
t.hide(outlineLeash);
}
@@ -1190,7 +1189,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
if (show) {
t.setAlpha(leash, 1.f);
- t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setLayer(leash, Integer.MAX_VALUE);
t.setPosition(leash, bounds.left, bounds.top);
t.show(leash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
index 8b36c9406b15..7b679580fa87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -227,10 +227,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ b.setParent(findTaskSurface(taskId));
+ }
+
+ @Override
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ t.reparent(sc, findTaskSurface(taskId));
+ }
+
+ private SurfaceControl findTaskSurface(int taskId) {
if (mRootTaskInfo.taskId == taskId) {
- b.setParent(mRootLeash);
+ return mRootLeash;
} else if (mChildrenLeashes.contains(taskId)) {
- b.setParent(mChildrenLeashes.get(taskId));
+ return mChildrenLeashes.get(taskId);
} else {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index e7b5744dd21b..014f02bcf8b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -56,7 +56,7 @@ import com.android.wm.shell.common.TransactionPool;
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private static final boolean DEBUG_EXIT_ANIMATION = false;
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
- private static final String TAG = StartingSurfaceDrawer.TAG;
+ private static final String TAG = StartingWindowController.TAG;
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
private static final Interpolator MASK_RADIUS_INTERPOLATOR =
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 b191cabcf6aa..b6c8cffb9699 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
@@ -18,10 +18,13 @@ package com.android.wm.shell.startingsurface;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+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;
@@ -46,6 +49,7 @@ import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -54,6 +58,7 @@ import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.View;
import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import com.android.internal.R;
@@ -61,9 +66,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
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.protolog.ShellProtoLogGroup;
import java.util.List;
import java.util.function.Consumer;
@@ -78,8 +86,7 @@ import java.util.function.UnaryOperator;
* @hide
*/
public class SplashscreenContentDrawer {
- private static final String TAG = StartingSurfaceDrawer.TAG;
- private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
+ private static final String TAG = StartingWindowController.TAG;
// 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
@@ -109,8 +116,10 @@ public class SplashscreenContentDrawer {
private final Handler mSplashscreenWorkerHandler;
@VisibleForTesting
final ColorCache mColorCache;
+ private final ShellExecutor mSplashScreenExecutor;
- SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
+ SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool,
+ ShellExecutor splashScreenExecutor) {
mContext = context;
mIconProvider = iconProvider;
mTransactionPool = pool;
@@ -123,6 +132,7 @@ public class SplashscreenContentDrawer {
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
+ mSplashScreenExecutor = splashScreenExecutor;
}
/**
@@ -137,8 +147,8 @@ public class SplashscreenContentDrawer {
* executed on splash screen thread. Note that the view can be
* null if failed.
*/
- void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
- int taskId, Consumer<SplashScreenView> splashScreenViewConsumer,
+ void createContentView(Context context, @StartingWindowType int suggestType,
+ StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer,
Consumer<Runnable> uiThreadInitConsumer) {
mSplashscreenWorkerHandler.post(() -> {
SplashScreenView contentView;
@@ -149,7 +159,7 @@ public class SplashscreenContentDrawer {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RuntimeException e) {
Slog.w(TAG, "failed creating starting window content at taskId: "
- + taskId, e);
+ + info.taskInfo.taskId, e);
contentView = null;
}
splashScreenViewConsumer.accept(contentView);
@@ -240,7 +250,7 @@ public class SplashscreenContentDrawer {
return null;
}
- private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai,
+ private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
@StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
@@ -249,6 +259,9 @@ public class SplashscreenContentDrawer {
final Drawable legacyDrawable = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
? peekLegacySplashscreenContent(context, mTmpAttrs) : null;
+ final ActivityInfo ai = info.targetActivityInfo != null
+ ? info.targetActivityInfo
+ : info.taskInfo.topActivityInfo;
final int themeBGColor = legacyDrawable != null
? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
: getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
@@ -257,6 +270,7 @@ public class SplashscreenContentDrawer {
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
.setUiThreadInitConsumer(uiThreadInitConsumer)
+ .setAllowHandleSolidColor(info.allowHandleSolidColorSplashScreen())
.build();
}
@@ -287,20 +301,15 @@ public class SplashscreenContentDrawer {
Color.TRANSPARENT);
attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
R.styleable.Window_windowSplashScreenAnimatedIcon), null);
- attrs.mAnimationDuration = safeReturnAttrDefault((def) -> typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, def), 0);
attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
R.styleable.Window_windowSplashScreenBrandingImage), null);
attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
R.styleable.Window_windowSplashScreenIconBackgroundColor, def),
Color.TRANSPARENT);
typedArray.recycle();
- if (DEBUG) {
- Slog.d(TAG, "window attributes color: "
- + Integer.toHexString(attrs.mWindowBgColor)
- + " icon " + attrs.mSplashScreenIcon + " duration " + attrs.mAnimationDuration
- + " brandImage " + attrs.mBrandingImage);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "getWindowAttrs: window attributes color: %s, replace icon: %b",
+ Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null);
}
/** Creates the wrapper with system theme to avoid unexpected styles from app. */
@@ -315,7 +324,33 @@ public class SplashscreenContentDrawer {
private Drawable mSplashScreenIcon = null;
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
- private int mAnimationDuration = 0;
+ }
+
+ /**
+ * Get an optimal animation duration to keep the splash screen from showing.
+ *
+ * @param animationDuration The animation duration defined from app.
+ * @param appReadyDuration The real duration from the starting the app to the first app window
+ * drawn.
+ */
+ @VisibleForTesting
+ static long getShowingDuration(long animationDuration, long appReadyDuration) {
+ if (animationDuration <= appReadyDuration) {
+ // app window ready took longer time than animation, it can be removed ASAP.
+ return appReadyDuration;
+ }
+ if (appReadyDuration < MAX_ANIMATION_DURATION) {
+ if (animationDuration > MAX_ANIMATION_DURATION
+ || appReadyDuration < MINIMAL_ANIMATION_DURATION) {
+ // animation is too long or too short, cut off with minimal duration
+ return MINIMAL_ANIMATION_DURATION;
+ }
+ // animation is longer than dOpt but shorter than max, allow it to play till finish
+ return MAX_ANIMATION_DURATION;
+ }
+ // the shortest duration is longer than dMax, cut off no matter how long the animation
+ // will be.
+ return appReadyDuration;
}
private class StartingWindowViewBuilder {
@@ -328,6 +363,7 @@ public class SplashscreenContentDrawer {
private Drawable[] mFinalIconDrawables;
private int mFinalIconSize = mIconSize;
private Consumer<Runnable> mUiThreadInitTask;
+ private boolean mAllowHandleSolidColor;
StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
@@ -354,18 +390,20 @@ public class SplashscreenContentDrawer {
return this;
}
+ StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
+ mAllowHandleSolidColor = allowHandleSolidColor;
+ return this;
+ }
+
SplashScreenView build() {
Drawable iconDrawable;
- final int animationDuration;
- if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
+ if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
// empty or legacy splash screen case
- animationDuration = 0;
mFinalIconSize = 0;
} else if (mTmpAttrs.mSplashScreenIcon != null) {
// Using the windowSplashScreenAnimatedIcon attribute
iconDrawable = mTmpAttrs.mSplashScreenIcon;
- animationDuration = mTmpAttrs.mAnimationDuration;
// There is no background below the icon, so scale the icon up
if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
@@ -385,23 +423,19 @@ public class SplashscreenContentDrawer {
iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
}
if (!processAdaptiveIcon(iconDrawable)) {
- if (DEBUG) {
- Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "The icon is not an AdaptiveIconDrawable");
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "legacy_icon_factory");
final ShapeIconFactory factory = new ShapeIconFactory(
SplashscreenContentDrawer.this.mContext,
scaledIconDpi, mFinalIconSize);
- final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(
- iconDrawable, true /* shrinkNonAdaptiveIcons */);
+ final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
createIconDrawable(new BitmapDrawable(bitmap), true);
}
- animationDuration = 0;
}
- return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration,
- mUiThreadInitTask);
+ return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask);
}
private class ShapeIconFactory extends BaseIconFactory {
@@ -416,8 +450,8 @@ public class SplashscreenContentDrawer {
iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
} else {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
- mTmpAttrs.mIconBgColor, mThemeColor,
- iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
+ mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
+ mFinalIconSize, mSplashscreenWorkerHandler);
}
}
@@ -435,14 +469,14 @@ public class SplashscreenContentDrawer {
() -> new DrawableColorTester(iconForeground,
DrawableColorTester.TRANSLUCENT_FILTER /* filterType */),
() -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
-
- if (DEBUG) {
- Slog.d(TAG, "FgMainColor=" + Integer.toHexString(iconColor.mFgColor)
- + " BgMainColor=" + Integer.toHexString(iconColor.mBgColor)
- + " IsBgComplex=" + iconColor.mIsBgComplex
- + " FromCache=" + (iconColor.mReuseCount > 0)
- + " ThemeColor=" + Integer.toHexString(mThemeColor));
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "processAdaptiveIcon: FgMainColor=%s, BgMainColor=%s, "
+ + "IsBgComplex=%b, FromCache=%b, ThemeColor=%s",
+ Integer.toHexString(iconColor.mFgColor),
+ Integer.toHexString(iconColor.mBgColor),
+ iconColor.mIsBgComplex,
+ iconColor.mReuseCount > 0,
+ Integer.toHexString(mThemeColor));
// Only draw the foreground of AdaptiveIcon to the splash screen if below condition
// meet:
@@ -456,9 +490,8 @@ public class SplashscreenContentDrawer {
&& (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
|| (iconColor.mIsBgGrayscale
&& !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
- if (DEBUG) {
- Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "processAdaptiveIcon: choose fg icon");
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
@@ -469,9 +502,8 @@ public class SplashscreenContentDrawer {
mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
createIconDrawable(iconForeground, false);
} else {
- if (DEBUG) {
- Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "processAdaptiveIcon: draw whole icon");
createIconDrawable(iconDrawable, false);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -479,7 +511,7 @@ public class SplashscreenContentDrawer {
}
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
- int animationDuration, Consumer<Runnable> uiThreadInitTask) {
+ Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null;
Drawable background = null;
if (iconDrawable != null) {
@@ -495,8 +527,8 @@ public class SplashscreenContentDrawer {
.setIconSize(iconSize)
.setIconBackground(background)
.setCenterViewDrawable(foreground)
- .setAnimationDurationMillis(animationDuration)
- .setUiThreadInitConsumer(uiThreadInitTask);
+ .setUiThreadInitConsumer(uiThreadInitTask)
+ .setAllowHandleSolidColor(mAllowHandleSolidColor);
if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
&& mTmpAttrs.mBrandingImage != null) {
@@ -504,9 +536,6 @@ public class SplashscreenContentDrawer {
mBrandingImageHeight);
}
final SplashScreenView splashScreenView = builder.build();
- if (DEBUG) {
- Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
- }
if (mSuggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
splashScreenView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@@ -536,10 +565,9 @@ public class SplashscreenContentDrawer {
final float lumB = Color.luminance(b);
final float contrastRatio = lumA > lumB
? (lumA + 0.05f) / (lumB + 0.05f) : (lumB + 0.05f) / (lumA + 0.05f);
- if (DEBUG) {
- Slog.d(TAG, "isRgbSimilarInHsv a: " + Integer.toHexString(a)
- + " b " + Integer.toHexString(b) + " contrast ratio: " + contrastRatio);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "isRgbSimilarInHsv a:%s, b:%s, contrast ratio:%f",
+ Integer.toHexString(a), Integer.toHexString(b), contrastRatio);
if (contrastRatio < 2) {
return true;
}
@@ -560,14 +588,11 @@ public class SplashscreenContentDrawer {
final double square = squareH + squareS + squareV;
final double mean = square / 3;
final double root = Math.sqrt(mean);
- if (DEBUG) {
- Slog.d(TAG, "hsvDiff " + minAngle
- + " ah " + aHsv[0] + " bh " + bHsv[0]
- + " as " + aHsv[1] + " bs " + bHsv[1]
- + " av " + aHsv[2] + " bv " + bHsv[2]
- + " sqH " + squareH + " sqS " + squareS + " sqV " + squareV
- + " root " + root);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "isRgbSimilarInHsv hsvDiff: %d, ah: %f, bh: %f, as: %f, bs: %f, av: %f, bv: %f, "
+ + "sqH: %f, sqS: %f, sqV: %f, rsm: %f",
+ minAngle, aHsv[0], bHsv[0], aHsv[1], bHsv[1], aHsv[2], bHsv[2],
+ squareH, squareS, squareV, root);
return root < 0.1;
}
@@ -598,9 +623,8 @@ public class SplashscreenContentDrawer {
if (drawable instanceof LayerDrawable) {
LayerDrawable layerDrawable = (LayerDrawable) drawable;
if (layerDrawable.getNumberOfLayers() > 0) {
- if (DEBUG) {
- Slog.d(TAG, "replace drawable with bottom layer drawable");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "DrawableColorTester: replace drawable with bottom layer drawable");
drawable = layerDrawable.getDrawable(0);
}
}
@@ -805,9 +829,8 @@ public class SplashscreenContentDrawer {
}
}
if (realSize == 0) {
- if (DEBUG) {
- Slog.d(TAG, "quantize: this is pure transparent image");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "DrawableTester quantize: pure transparent image");
mInnerQuantizer.quantize(pixels, maxColors);
return;
}
@@ -979,9 +1002,27 @@ public class SplashscreenContentDrawer {
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback) {
- final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext, view,
- leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
- animation.startAnimations();
+ Rect frame, Runnable finishCallback, long createTime) {
+ final Runnable playAnimation = () -> {
+ final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ animation.startAnimations();
+ };
+ if (view.getIconView() == null) {
+ playAnimation.run();
+ return;
+ }
+ final long appReadyDuration = SystemClock.uptimeMillis() - createTime;
+ final long animDuration = view.getIconAnimationDuration() != null
+ ? view.getIconAnimationDuration().toMillis() : 0;
+ final long minimumShowingDuration = getShowingDuration(animDuration, appReadyDuration);
+ final long delayed = minimumShowingDuration - appReadyDuration;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "applyExitAnimation delayed: %s", delayed);
+ if (delayed > 0) {
+ view.postDelayed(playAnimation, delayed);
+ } else {
+ playAnimation.run();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 709e2219a64e..5f52071bf950 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -18,9 +18,7 @@ package com.android.wm.shell.startingsurface;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,8 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
@@ -45,6 +45,8 @@ import android.window.SplashScreenView;
import com.android.internal.R;
+import java.util.function.LongConsumer;
+
/**
* Creating a lightweight Drawable object used for splash screen.
*
@@ -52,7 +54,7 @@ import com.android.internal.R;
*/
public class SplashscreenIconDrawableFactory {
- private static final String TAG = "SplashscreenIconDrawableFactory";
+ private static final String TAG = StartingWindowController.TAG;
/**
* @return An array containing the foreground drawable at index 0 and if needed a background
@@ -266,99 +268,100 @@ public class SplashscreenIconDrawableFactory {
*/
public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
implements SplashScreenView.IconAnimateListener {
- private Animatable mAnimatableIcon;
- private Animator mIconAnimator;
+ private final Animatable mAnimatableIcon;
private boolean mAnimationTriggered;
private AnimatorListenerAdapter mJankMonitoringListener;
+ private boolean mRunning;
+ private LongConsumer mStartListener;
AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
super(foregroundDrawable);
- mForegroundDrawable.setCallback(mCallback);
- }
-
- @Override
- public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {
- mJankMonitoringListener = listener;
- }
-
- @Override
- public boolean prepareAnimate(long duration, Runnable startListener) {
- mAnimatableIcon = (Animatable) mForegroundDrawable;
- mIconAnimator = ValueAnimator.ofInt(0, 1);
- mIconAnimator.setDuration(duration);
- mIconAnimator.addListener(new Animator.AnimatorListener() {
+ Callback callback = new Callback() {
@Override
- public void onAnimationStart(Animator animation) {
- if (startListener != null) {
- startListener.run();
- }
- try {
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationStart(animation);
- }
- mAnimatableIcon.start();
- } catch (Exception ex) {
- Log.e(TAG, "Error while running the splash screen animated icon", ex);
- animation.cancel();
- }
+ public void invalidateDrawable(@NonNull Drawable who) {
+ invalidateSelf();
}
@Override
- public void onAnimationEnd(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationEnd(animation);
- }
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
+ long when) {
+ scheduleSelf(what, when);
}
@Override
- public void onAnimationCancel(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationCancel(animation);
- }
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
}
+ };
+ mForegroundDrawable.setCallback(callback);
+ mAnimatableIcon = (Animatable) mForegroundDrawable;
+ }
- @Override
- public void onAnimationRepeat(Animator animation) {
- // do not repeat
- mAnimatableIcon.stop();
- }
- });
- return true;
+ @Override
+ public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {
+ mJankMonitoringListener = listener;
}
@Override
- public void stopAnimation() {
- if (mIconAnimator != null && mIconAnimator.isRunning()) {
- mIconAnimator.end();
- mJankMonitoringListener = null;
- }
+ public void prepareAnimate(LongConsumer startListener) {
+ stopAnimation();
+ mStartListener = startListener;
}
- private final Callback mCallback = new Callback() {
- @Override
- public void invalidateDrawable(@NonNull Drawable who) {
- invalidateSelf();
+ private void startAnimation() {
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationStart(null);
+ }
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ mRunning = false;
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationCancel(null);
+ }
+ if (mStartListener != null) {
+ mStartListener.accept(0);
+ }
+ return;
}
+ long animDuration = 0;
+ if (mAnimatableIcon instanceof AnimatedVectorDrawable
+ && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration();
+ } else if (mAnimatableIcon instanceof AnimationDrawable
+ && ((AnimationDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration();
+ }
+ mRunning = true;
+ if (mStartListener != null) {
+ mStartListener.accept(animDuration);
+ }
+ }
- @Override
- public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
- scheduleSelf(what, when);
+ private void onAnimationEnd() {
+ mAnimatableIcon.stop();
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationEnd(null);
}
+ mStartListener = null;
+ mRunning = false;
+ }
- @Override
- public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
- unscheduleSelf(what);
+ @Override
+ public void stopAnimation() {
+ if (mRunning) {
+ onAnimationEnd();
+ mJankMonitoringListener = null;
}
- };
+ }
private void ensureAnimationStarted() {
if (mAnimationTriggered) {
return;
}
- if (mIconAnimator != null && !mIconAnimator.isRunning()) {
- mIconAnimator.start();
+ if (!mRunning) {
+ startAnimation();
}
mAnimationTriggered = 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 270107c01335..04d6ef7f9505 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
@@ -41,6 +41,7 @@ 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;
@@ -61,10 +62,12 @@ 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.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;
@@ -106,9 +109,7 @@ import java.util.function.Supplier;
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
- static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
- static final boolean DEBUG_SPLASH_SCREEN = StartingWindowController.DEBUG_SPLASH_SCREEN;
- static final boolean DEBUG_TASK_SNAPSHOT = StartingWindowController.DEBUG_TASK_SNAPSHOT;
+ private static final String TAG = StartingWindowController.TAG;
private final Context mContext;
private final DisplayManager mDisplayManager;
@@ -121,6 +122,25 @@ public class StartingSurfaceDrawer {
private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
/**
+ * 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;
+
+ /**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
@@ -128,7 +148,8 @@ public class StartingSurfaceDrawer {
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
+ mSplashScreenExecutor);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY);
@@ -179,11 +200,9 @@ public class StartingSurfaceDrawer {
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- if (DEBUG_SPLASH_SCREEN) {
- Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
- + " theme=" + Integer.toHexString(theme) + " task=" + taskInfo.taskId
- + " suggestType=" + suggestType);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
+ activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
final Display display = getDisplay(displayId);
if (display == null) {
// Can't show splash screen on requested display, so skip showing at all.
@@ -208,10 +227,9 @@ public class StartingSurfaceDrawer {
final Configuration taskConfig = taskInfo.getConfiguration();
if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
- if (DEBUG_SPLASH_SCREEN) {
- Slog.d(TAG, "addSplashScreen: creating context based"
- + " on task Configuration " + taskConfig + " for splash screen");
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: creating context based on task Configuration %s",
+ taskConfig);
final Context overrideContext = context.createConfigurationContext(taskConfig);
overrideContext.setTheme(theme);
final TypedArray typedArray = overrideContext.obtainStyledAttributes(
@@ -222,10 +240,9 @@ public class StartingSurfaceDrawer {
// We want to use the windowBackground for the override context if it is
// available, otherwise we use the default one to make sure a themed starting
// window is displayed for the app.
- if (DEBUG_SPLASH_SCREEN) {
- Slog.d(TAG, "addSplashScreen: apply overrideConfig"
- + taskConfig + " to starting window resId=" + resId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: apply overrideConfig %s",
+ taskConfig);
context = overrideContext;
}
} catch (Resources.NotFoundException e) {
@@ -273,6 +290,8 @@ public class StartingSurfaceDrawer {
// touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
// flag because we do know that the next window will take input
// focus, so we want to get the IME window up on top of us right away.
+ // Touches will only pass through to the host activity window and will be blocked from
+ // passing to any other windows.
windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -280,9 +299,6 @@ public class StartingSurfaceDrawer {
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -333,7 +349,7 @@ public class StartingSurfaceDrawer {
if (mSysuiProxy != null) {
mSysuiProxy.requestTopUi(true, TAG);
}
- mSplashscreenContentDrawer.createContentView(context, suggestType, activityInfo, taskId,
+ mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
viewSupplier::setView, viewSupplier::setUiThreadInitTask);
try {
if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
@@ -349,6 +365,12 @@ public class StartingSurfaceDrawer {
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
final SplashScreenView contentView = viewSupplier.get();
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
@@ -461,10 +483,9 @@ public class StartingSurfaceDrawer {
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "Task start finish, remove starting surface for task "
- + removalInfo.taskId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Task start finish, remove starting surface for task: %d",
+ removalInfo.taskId);
removeWindowSynced(removalInfo, false /* immediately */);
}
@@ -472,9 +493,8 @@ public class StartingSurfaceDrawer {
* Clear all starting windows immediately.
*/
public void clearAllWindows() {
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "Clear all starting windows immediately");
- }
+ 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) {
@@ -502,10 +522,9 @@ public class StartingSurfaceDrawer {
} else {
parcelable = null;
}
- if (DEBUG_SPLASH_SCREEN) {
- Slog.v(TAG, "Copying splash screen window view for task: " + taskId
- + " parcelable: " + parcelable);
- }
+ 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);
}
@@ -531,11 +550,9 @@ public class StartingSurfaceDrawer {
return;
}
mAnimatedSplashScreenSurfaceHosts.remove(taskId);
- if (DEBUG_SPLASH_SCREEN) {
- String reason = fromServer ? "Server cleaned up" : "App removed";
- Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:"
- + 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);
}
@@ -593,9 +610,8 @@ public class StartingSurfaceDrawer {
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
if (record != null) {
if (record.mDecorView != null) {
- if (DEBUG_SPLASH_SCREEN) {
- Slog.v(TAG, "Removing splash screen window for task: " + taskId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Removing splash screen window for task: %d", taskId);
if (record.mContentView != null) {
if (immediately
|| record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
@@ -604,7 +620,8 @@ public class StartingSurfaceDrawer {
if (removalInfo.playRevealAnimation) {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
- () -> removeWindowInner(record.mDecorView, true));
+ () -> removeWindowInner(record.mDecorView, true),
+ record.mCreateTime);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
@@ -619,9 +636,8 @@ public class StartingSurfaceDrawer {
mStartingWindowRecords.remove(taskId);
}
if (record.mTaskSnapshotWindow != null) {
- if (DEBUG_TASK_SNAPSHOT) {
- Slog.v(TAG, "Removing task snapshot window for " + taskId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Removing task snapshot window for %d", taskId);
if (immediately) {
record.mTaskSnapshotWindow.removeImmediately();
} else {
@@ -653,6 +669,7 @@ public class StartingSurfaceDrawer {
private boolean mSetSplashScreen;
private @StartingWindowType int mSuggestType;
private int mBGColor;
+ private final long mCreateTime;
StartingWindowRecord(IBinder appToken, View decorView,
TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
@@ -663,6 +680,7 @@ public class StartingSurfaceDrawer {
mBGColor = mTaskSnapshotWindow.getBackgroundColor();
}
mSuggestType = suggestType;
+ mCreateTime = SystemClock.uptimeMillis();
}
private void setSplashScreenView(SplashScreenView splashScreenView) {
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 b0a66059a466..fbc992378e50 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
@@ -16,10 +16,10 @@
package com.android.wm.shell.startingsurface;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
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 com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
@@ -64,10 +64,7 @@ import com.android.wm.shell.common.TransactionPool;
* @hide
*/
public class StartingWindowController implements RemoteCallable<StartingWindowController> {
- private static final String TAG = StartingWindowController.class.getSimpleName();
-
- public static final boolean DEBUG_SPLASH_SCREEN = false;
- public static final boolean DEBUG_TASK_SNAPSHOT = false;
+ public static final String TAG = "ShellStartingWindow";
private static final long TASK_BG_COLOR_RETAIN_TIME_MS = 5000;
@@ -158,7 +155,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private static boolean isSplashScreenType(@StartingWindowType int suggestionType) {
return suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
- || suggestionType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
+ || suggestionType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
|| suggestionType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
}
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 3e88c464d359..2debcf296ea8 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
@@ -62,6 +62,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.HardwareBuffer;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
@@ -85,8 +86,10 @@ import android.window.TaskSnapshot;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DecorView;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.view.BaseIWindow;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* This class represents a starting window that shows a snapshot.
@@ -113,8 +116,7 @@ public class TaskSnapshotWindow {
| FLAG_SCALED
| FLAG_SECURE;
- private static final String TAG = StartingSurfaceDrawer.TAG;
- private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_TASK_SNAPSHOT;
+ private static final String TAG = StartingWindowController.TAG;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
@@ -125,9 +127,6 @@ public class TaskSnapshotWindow {
*/
private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
- //tmp vars for unused relayout params
- private static final Point TMP_SURFACE_SIZE = new Point();
-
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
@@ -158,9 +157,8 @@ public class TaskSnapshotWindow {
@NonNull Runnable clearWindowHandler) {
final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
final int taskId = runningTaskInfo.taskId;
- if (DEBUG) {
- Slog.d(TAG, "create taskSnapshot surface for task: " + taskId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "create taskSnapshot surface for task: %d", taskId);
final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
@@ -244,9 +242,9 @@ public class TaskSnapshotWindow {
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, TMP_SURFACE_SIZE);
+ tmpControls, new Bundle());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
@@ -327,17 +325,15 @@ public class TaskSnapshotWindow {
? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
: DELAY_REMOVAL_TIME_GENERAL;
mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
- if (DEBUG) {
- Slog.d(TAG, "Defer removing snapshot surface in " + delayRemovalTime);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Defer removing snapshot surface in %d", delayRemovalTime);
}
void removeImmediately() {
mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
- if (DEBUG) {
- Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
mSession.remove(mWindow);
} catch (RemoteException e) {
// nothing
@@ -363,9 +359,8 @@ public class TaskSnapshotWindow {
}
private void drawSnapshot() {
- if (DEBUG) {
- Slog.d(TAG, "Drawing snapshot surface sizeMismatch= " + mSizeMismatch);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch);
if (mSizeMismatch) {
// The dimensions of the buffer and the window don't match, so attaching the buffer
// will fail. Better create a child window with the exact dimensions and fill the parent
@@ -383,9 +378,7 @@ public class TaskSnapshotWindow {
}
private void drawSizeMatchSnapshot() {
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- mSnapshot.getHardwareBuffer());
- mTransaction.setBuffer(mSurfaceControl, graphicBuffer)
+ mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
.setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
.apply();
}
@@ -431,20 +424,20 @@ public class TaskSnapshotWindow {
// Scale the mismatch dimensions to fill the task bounds
mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- mSnapshot.getHardwareBuffer());
mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
- mTransaction.setBuffer(childSurfaceControl, graphicBuffer);
+ mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
if (aspectRatioMismatch) {
GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
+ // TODO: Support this on HardwareBuffer
final Canvas c = background.lockCanvas();
drawBackgroundAndBars(c, frame);
background.unlockCanvasAndPost(c);
- mTransaction.setBuffer(mSurfaceControl, background);
+ mTransaction.setBuffer(mSurfaceControl,
+ HardwareBuffer.createFromGraphicBuffer(background));
}
mTransaction.apply();
childSurfaceControl.release();
@@ -525,7 +518,7 @@ public class TaskSnapshotWindow {
private void reportDrawn() {
try {
- mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
+ mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE);
} catch (RemoteException e) {
clearWindowSynced();
}
@@ -541,8 +534,9 @@ public class TaskSnapshotWindow {
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
- MergedConfiguration mergedConfiguration, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId) {
+ MergedConfiguration mergedConfiguration, InsetsState insetsState,
+ boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
+ int resizeMode) {
if (mOuter != null) {
mOuter.mSplashScreenExecutor.execute(() -> {
if (mergedConfiguration != 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 bde2b5ff4d60..51722c46a7ae 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
@@ -17,35 +17,32 @@
package com.android.wm.shell.startingsurface.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
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.TYPE_PARAMETER_ACTIVITY_CREATED;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
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_EMPTY_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
-import static com.android.wm.shell.startingsurface.StartingWindowController.DEBUG_SPLASH_SCREEN;
-import static com.android.wm.shell.startingsurface.StartingWindowController.DEBUG_TASK_SNAPSHOT;
-
-import android.util.Slog;
import android.window.StartingWindowInfo;
import android.window.TaskSnapshot;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
/**
* Algorithm for determining the type of a new starting window on handheld devices.
- * At the moment also used on Android Auto.
+ * At the moment also used on Android Auto and Wear OS.
*/
public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
- private static final String TAG = PhoneStartingWindowTypeAlgorithm.class.getSimpleName();
-
@Override
public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
final int parameter = windowInfo.startingWindowTypeParameter;
@@ -54,43 +51,57 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0;
final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0;
final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0;
- final boolean useEmptySplashScreen =
- (parameter & TYPE_PARAMETER_USE_EMPTY_SPLASH_SCREEN) != 0;
+ final boolean isSolidColorSplashScreen =
+ (parameter & TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN) != 0;
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
+ final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "preferredStartingWindowType newTask:" + newTask
- + " taskSwitch:" + taskSwitch
- + " processRunning:" + processRunning
- + " allowTaskSnapshot:" + allowTaskSnapshot
- + " activityCreated:" + activityCreated
- + " useEmptySplashScreen:" + useEmptySplashScreen
- + " legacySplashScreen:" + legacySplashScreen
- + " topIsHome:" + topIsHome);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "preferredStartingWindowType "
+ + "newTask=%b, "
+ + "taskSwitch=%b, "
+ + "processRunning=%b, "
+ + "allowTaskSnapshot=%b, "
+ + "activityCreated=%b, "
+ + "isSolidColorSplashScreen=%b, "
+ + "legacySplashScreen=%b, "
+ + "activityDrawn=%b, "
+ + "topIsHome=%b",
+ newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
- return useEmptySplashScreen
- ? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
- : legacySplashScreen
- ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+ return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
}
}
- if (taskSwitch && allowTaskSnapshot) {
- if (isSnapshotCompatible(windowInfo)) {
- return STARTING_WINDOW_TYPE_SNAPSHOT;
+
+ if (taskSwitch) {
+ if (allowTaskSnapshot) {
+ if (isSnapshotCompatible(windowInfo)) {
+ return STARTING_WINDOW_TYPE_SNAPSHOT;
+ }
+ if (!topIsHome) {
+ return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
+ }
}
- if (!topIsHome) {
- return STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+ if (!activityDrawn && !topIsHome) {
+ return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
}
}
return STARTING_WINDOW_TYPE_NONE;
}
+ private static int getSplashscreenType(boolean solidColorSplashScreen,
+ boolean legacySplashScreen) {
+ return solidColorSplashScreen
+ ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
+ : legacySplashScreen
+ ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+ }
/**
* Returns {@code true} if the task snapshot is compatible with this activity (at least the
@@ -99,26 +110,24 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
private boolean isSnapshotCompatible(StartingWindowInfo windowInfo) {
final TaskSnapshot snapshot = windowInfo.taskSnapshot;
if (snapshot == null) {
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "isSnapshotCompatible no snapshot, taskId=%d",
+ windowInfo.taskInfo.taskId);
return false;
}
if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) {
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot "
- + windowInfo.taskInfo.topActivity);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "isSnapshotCompatible obsoleted snapshot for %s",
+ windowInfo.taskInfo.topActivity);
return false;
}
final int taskRotation = windowInfo.taskInfo.configuration
.windowConfiguration.getRotation();
final int snapshotRotation = snapshot.getRotation();
- if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation
- + " snapshot " + snapshotRotation);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "isSnapshotCompatible taskRotation=%d, snapshotRotation=%d",
+ taskRotation, snapshotRotation);
return taskRotation == snapshotRotation;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
index 6e7dec590308..74fe8fbbd5e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.startingsurface.tv;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import android.window.StartingWindowInfo;
@@ -30,6 +30,6 @@ public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorith
@Override
public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
// For now we want to always show empty splash screens on TV.
- return STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+ return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
new file mode 100644
index 000000000000..19133e29de4b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.RotationUtils;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.util.CounterRotator;
+
+import java.util.List;
+
+/**
+ * The helper class that performs counter-rotate for all "going-away" window containers if they are
+ * still in the old rotation in a transition.
+ */
+public class CounterRotatorHelper {
+ private final ArrayMap<WindowContainerToken, CounterRotator> mRotatorMap = new ArrayMap<>();
+ private final Rect mLastDisplayBounds = new Rect();
+ private int mLastRotationDelta;
+
+ /** Puts the surface controls of closing changes to counter-rotated surfaces. */
+ public void handleClosingChanges(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull TransitionInfo.Change displayRotationChange) {
+ final int rotationDelta = RotationUtils.deltaRotation(
+ displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation());
+ final Rect displayBounds = displayRotationChange.getEndAbsBounds();
+ final int displayW = displayBounds.width();
+ final int displayH = displayBounds.height();
+ mLastRotationDelta = rotationDelta;
+ mLastDisplayBounds.set(displayBounds);
+
+ final List<TransitionInfo.Change> changes = info.getChanges();
+ final int numChanges = changes.size();
+ for (int i = numChanges - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = changes.get(i);
+ final WindowContainerToken parent = change.getParent();
+ if (!Transitions.isClosingType(change.getMode())
+ || !TransitionInfo.isIndependent(change, info) || parent == null) {
+ continue;
+ }
+
+ CounterRotator crot = mRotatorMap.get(parent);
+ if (crot == null) {
+ crot = new CounterRotator();
+ crot.setup(startTransaction, info.getChange(parent).getLeash(), rotationDelta,
+ displayW, displayH);
+ final SurfaceControl rotatorSc = crot.getSurface();
+ if (rotatorSc != null) {
+ // Wallpaper should be placed at the bottom.
+ final int layer = (change.getFlags() & FLAG_IS_WALLPAPER) == 0
+ ? numChanges - i
+ : -1;
+ startTransaction.setLayer(rotatorSc, layer);
+ }
+ mRotatorMap.put(parent, crot);
+ }
+ crot.addChild(startTransaction, change.getLeash());
+ }
+ }
+
+ /**
+ * Returns the rotated end bounds if the change is put in previous rotation. Otherwise the
+ * original end bounds are returned.
+ */
+ @NonNull
+ public Rect getEndBoundsInStartRotation(@NonNull TransitionInfo.Change change) {
+ if (mLastRotationDelta == 0) return change.getEndAbsBounds();
+ final Rect rotatedBounds = new Rect(change.getEndAbsBounds());
+ RotationUtils.rotateBounds(rotatedBounds, mLastDisplayBounds, mLastRotationDelta);
+ return rotatedBounds;
+ }
+
+ /**
+ * Removes the counter rotation surface in the finish transaction. No need to reparent the
+ * children as the finish transaction should have already taken care of that.
+ *
+ * This can only be called after startTransaction for {@link #handleClosingChanges} is applied.
+ */
+ public void cleanUp(@NonNull SurfaceControl.Transaction finishTransaction) {
+ for (int i = mRotatorMap.size() - 1; i >= 0; --i) {
+ mRotatorMap.valueAt(i).cleanUp(finishTransaction);
+ }
+ mRotatorMap.clear();
+ mLastRotationDelta = 0;
+ }
+}
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 7abda994bb5e..542ddeea769c 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
@@ -18,11 +18,19 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -41,7 +49,6 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
-import static android.window.TransitionInfo.isIndependent;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
@@ -52,32 +59,47 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
+import android.os.Handler;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.view.WindowManager.TransitionType;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo;
import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.AttributeCache;
+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.DisplayController;
@@ -85,9 +107,10 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.CounterRotator;
import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@@ -114,12 +137,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
+ private final DevicePolicyManager mDevicePolicyManager;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
/** Keeps track of the currently-running animations associated with each transition. */
private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
+ private final CounterRotatorHelper mRotator = new CounterRotatorHelper();
private final Rect mInsets = new Rect(0, 0, 0, 0);
private float mTransitionAnimationScaleSetting = 1.0f;
@@ -127,9 +152,23 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private ScreenRotationAnimation mRotationAnimation;
+ private Drawable mEnterpriseThumbnailDrawable;
+
+ private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getIntExtra(EXTRA_RESOURCE_TYPE, /* default= */ -1)
+ != EXTRA_RESOURCE_TYPE_DRAWABLE) {
+ return;
+ }
+ updateEnterpriseThumbnailDrawable();
+ }
+ };
+
DefaultTransitionHandler(@NonNull DisplayController displayController,
@NonNull TransactionPool transactionPool, Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
@@ -138,9 +177,23 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ updateEnterpriseThumbnailDrawable();
+ mContext.registerReceiver(
+ mEnterpriseResourceUpdatedReceiver,
+ new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
+ /* broadcastPermission = */ null,
+ mainHandler);
+
AttributeCache.init(context);
}
+ private void updateEnterpriseThumbnailDrawable() {
+ mEnterpriseThumbnailDrawable = mDevicePolicyManager.getResources().getDrawable(
+ WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+ () -> mContext.getDrawable(R.drawable.ic_corp_badge));
+ }
+
@VisibleForTesting
static boolean isRotationSeamless(@NonNull TransitionInfo info,
DisplayController displayController) {
@@ -148,6 +201,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
"Display is changing, check if it should be seamless.");
boolean checkedDisplayLayout = false;
boolean hasTask = false;
+ boolean displayExplicitSeamless = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -156,7 +210,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// This container isn't rotating, so we can ignore it.
if (change.getEndRotation() == change.getStartRotation()) continue;
-
if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
// In the presence of System Alert windows we can not seamlessly rotate.
if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
@@ -164,6 +217,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
" display has system alert windows, so not seamless.");
return false;
}
+ displayExplicitSeamless =
+ change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
} else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -215,8 +270,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
- // ROTATION_ANIMATION_SEAMLESS can only be requested by task.
- if (hasTask) {
+ // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
+ if (hasTask || displayExplicitSeamless) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
return true;
}
@@ -273,16 +328,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final ArrayList<Animator> animations = new ArrayList<>();
mAnimations.put(transition, animations);
- final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>();
-
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
- for (int i = 0; i < counterRotators.size(); ++i) {
- counterRotators.valueAt(i).cleanUp(info.getRootLeash());
- }
- counterRotators.clear();
-
if (mRotationAnimation != null) {
mRotationAnimation.kill();
mRotationAnimation = null;
@@ -292,57 +340,65 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
+ final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+ new ArrayList<>();
+
+ @ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ final boolean isTask = change.getTaskInfo() != null;
+ boolean isSeamlessDisplayChange = false;
if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
- int rotateDelta = change.getEndRotation() - change.getStartRotation();
- int displayW = change.getEndAbsBounds().width();
- int displayH = change.getEndAbsBounds().height();
if (info.getType() == TRANSIT_CHANGE) {
- boolean isSeamless = isRotationSeamless(info, mDisplayController);
+ isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
final int anim = getRotationAnimation(info);
- if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
+ if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash());
+ mTransactionPool, startTransaction, change, info.getRootLeash(),
+ anim);
mRotationAnimation.startAnimation(animations, onAnimFinish,
mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
continue;
}
} else {
- // opening/closing an app into a new orientation. Counter-rotate all
- // "going-away" things since they are still in the old orientation.
- for (int j = info.getChanges().size() - 1; j >= 0; --j) {
- final TransitionInfo.Change innerChange = info.getChanges().get(j);
- if (!Transitions.isClosingType(innerChange.getMode())
- || !isIndependent(innerChange, info)
- || innerChange.getParent() == null) {
- continue;
- }
- CounterRotator crot = counterRotators.get(innerChange.getParent());
- if (crot == null) {
- crot = new CounterRotator();
- crot.setup(startTransaction,
- info.getChange(innerChange.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (crot.getSurface() != null) {
- int layer = info.getChanges().size() - j;
- startTransaction.setLayer(crot.getSurface(), layer);
- }
- counterRotators.put(innerChange.getParent(), crot);
- }
- crot.addChild(startTransaction, innerChange.getLeash());
- }
+ // Opening/closing an app into a new orientation.
+ mRotator.handleClosingChanges(info, startTransaction, change);
}
}
if (change.getMode() == TRANSIT_CHANGE) {
+ // If task is child task, only set position in parent and update crop when needed.
+ if (isTask && change.getParent() != null
+ && info.getChange(change.getParent()).getTaskInfo() != null) {
+ final Point positionInParent = change.getTaskInfo().positionInParent;
+ startTransaction.setPosition(change.getLeash(),
+ positionInParent.x, positionInParent.y);
+
+ if (!change.getEndAbsBounds().equals(
+ info.getChange(change.getParent()).getEndAbsBounds())) {
+ startTransaction.setWindowCrop(change.getLeash(),
+ change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+ }
+
+ continue;
+ }
+
+ // There is no default animation for Pip window in rotation transition, and the
+ // PipTransition will update the surface of its own window at start/finish.
+ if (isTask && change.getTaskInfo().configuration.windowConfiguration
+ .getWindowingMode() == WINDOWING_MODE_PINNED) {
+ continue;
+ }
// No default animation for this, so just update bounds/position.
startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - change.getEndRelOffset().x,
- change.getEndAbsBounds().top - change.getEndRelOffset().y);
- if (change.getTaskInfo() != null) {
+ change.getEndAbsBounds().left - info.getRootOffset().x,
+ change.getEndAbsBounds().top - info.getRootOffset().y);
+ // Seamless display transition doesn't need to animate.
+ if (isSeamlessDisplayChange) continue;
+ if (isTask) {
// Skip non-tasks since those usually have null bounds.
startTransaction.setWindowCrop(change.getLeash(),
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
@@ -354,21 +410,249 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
Animation a = loadAnimation(info, change, wallpaperTransit);
if (a != null) {
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */);
+ if (isTask) {
+ final @TransitionType int type = info.getType();
+ final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN
+ || type == TRANSIT_CLOSE
+ || type == TRANSIT_TO_FRONT
+ || type == TRANSIT_TO_BACK;
+ final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
+ if (isOpenOrCloseTransition && !isTranslucent) {
+ // Use the overview background as the background for the animation
+ final Context uiContext = ActivityThread.currentActivityThread()
+ .getSystemUiContext();
+ backgroundColorForTransition =
+ uiContext.getColor(R.color.overview_background);
+ }
+ }
+
+ final float cornerRadius;
+ if (a.hasRoundedCorners() && isTask) {
+ // hasRoundedCorners is currently only enabled for tasks
+ final Context displayContext =
+ mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
+ cornerRadius = displayContext == null ? 0
+ : ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
+ } else {
+ cornerRadius = 0;
+ }
+
+ if (a.getShowBackground()) {
+ if (info.getAnimationOptions().getBackgroundColor() != 0) {
+ // If available use the background color provided through AnimationOptions
+ backgroundColorForTransition =
+ info.getAnimationOptions().getBackgroundColor();
+ } else if (a.getBackgroundColor() != 0) {
+ // Otherwise fallback on the background color provided through the animation
+ // definition.
+ backgroundColorForTransition = a.getBackgroundColor();
+ } else if (change.getBackgroundColor() != 0) {
+ // Otherwise default to the window's background color if provided through
+ // the theme as the background color for the animation - the top most window
+ // with a valid background color and showBackground set takes precedence.
+ backgroundColorForTransition = change.getBackgroundColor();
+ }
+ }
+
+ boolean delayedEdgeExtension = false;
+ if (!isTask && a.hasExtension()) {
+ if (!Transitions.isOpeningType(change.getMode())) {
+ // Can screenshot now (before startTransaction is applied)
+ edgeExtendWindow(change, a, startTransaction, finishTransaction);
+ } else {
+ // Need to screenshot after startTransaction is applied otherwise activity
+ // may not be visible or ready yet.
+ postStartTransactionCallbacks
+ .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+ delayedEdgeExtension = true;
+ }
+ }
+
+ final Rect clipRect = Transitions.isClosingType(change.getMode())
+ ? mRotator.getEndBoundsInStartRotation(change)
+ : change.getEndAbsBounds();
+
+ if (delayedEdgeExtension) {
+ // If the edge extension needs to happen after the startTransition has been
+ // applied, then we want to only start the animation after the edge extension
+ // postStartTransaction callback has been run
+ postStartTransactionCallbacks.add(t ->
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor,
+ null /* position */, cornerRadius, clipRect));
+ } else {
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
+ cornerRadius, clipRect);
+ }
if (info.getAnimationOptions() != null) {
- attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
+ attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
+ cornerRadius);
}
}
}
- startTransaction.apply();
+
+ if (backgroundColorForTransition != 0) {
+ addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
+ startTransaction, finishTransaction);
+ }
+
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0;
+ startTransaction.apply(waitForStartTransactionApply);
+
+ // Run tasks that require startTransaction to already be applied
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ postStartTransactionCallback.accept(t);
+ t.apply();
+ mTransactionPool.release(t);
+ }
+
+ mRotator.cleanUp(finishTransaction);
TransitionMetrics.getInstance().reportAnimationStart(transition);
// run finish now in-case there are no animations
onAnimFinish.run();
return true;
}
+ private void edgeExtendWindow(TransitionInfo.Change change,
+ Animation a, SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final Transformation transformationAtStart = new Transformation();
+ a.getTransformationAt(0, transformationAtStart);
+ final Transformation transformationAtEnd = new Transformation();
+ a.getTransformationAt(1, transformationAtEnd);
+
+ // We want to create an extension surface that is the maximal size and the animation will
+ // take care of cropping any part that overflows.
+ final Insets maxExtensionInsets = Insets.min(
+ transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+ final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+ change.getEndAbsBounds().height());
+ final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+ change.getEndAbsBounds().width());
+ if (maxExtensionInsets.left < 0) {
+ final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.left, targetSurfaceHeight);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Left Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.top < 0) {
+ final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.top);
+ final int xPos = 0;
+ final int yPos = maxExtensionInsets.top;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Top Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.right < 0) {
+ final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.right, targetSurfaceHeight);
+ final int xPos = targetSurfaceWidth;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Right Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.bottom < 0) {
+ final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.bottom);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = targetSurfaceHeight;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Bottom Edge Extension", startTransaction, finishTransaction);
+ }
+ }
+
+ private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
+ Rect extensionRect, int xPos, int yPos, String layerName,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setParent(surfaceToExtend)
+ .setHidden(true)
+ .setCallsite("DefaultTransitionHandler#startAnimation")
+ .setOpaque(true)
+ .setBufferSize(extensionRect.width(), extensionRect.height())
+ .build();
+
+ SurfaceControl.LayerCaptureArgs captureArgs =
+ new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ .setSourceCrop(edgeBounds)
+ .setFrameScale(1)
+ .setPixelFormat(PixelFormat.RGBA_8888)
+ .setChildrenOnly(true)
+ .setAllowProtected(true)
+ .build();
+ final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+ SurfaceControl.captureLayers(captureArgs);
+
+ if (edgeBuffer == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Failed to capture edge of window.");
+ return null;
+ }
+
+ android.graphics.BitmapShader shader =
+ new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
+ android.graphics.Shader.TileMode.CLAMP,
+ android.graphics.Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
+
+ final Surface surface = new Surface(edgeExtensionLayer);
+ Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
+
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+ finishTransaction.remove(edgeExtensionLayer);
+
+ return edgeExtensionLayer;
+ }
+
+ private void addBackgroundToTransition(
+ @NonNull SurfaceControl rootLeash,
+ @ColorInt int color,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction
+ ) {
+ final Color bgColor = Color.valueOf(color);
+ final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
+
+ final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
+ .setName("Animation Background")
+ .setParent(rootLeash)
+ .setColorLayer()
+ .setOpaque(true)
+ .build();
+
+ startTransaction
+ .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
+ .setColor(animationBackgroundSurface, colorArray)
+ .show(animationBackgroundSurface);
+ finishTransaction.remove(animationBackgroundSurface);
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -396,6 +680,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
+ final Rect endBounds = Transitions.isClosingType(changeMode)
+ ? mRotator.getEndBoundsInStartRotation(change)
+ : change.getEndAbsBounds();
if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -413,8 +700,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a = new AlphaAnimation(1.f, 1.f);
a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
} else if (type == TRANSIT_RELAUNCH) {
- a = mTransitionAnimation.createRelaunchAnimation(
- change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
+ a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
} else if (overrideType == ANIM_CUSTOM
&& (canCustomContainer || options.getOverrideTaskTransition())) {
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
@@ -423,80 +709,93 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
} else if (overrideType == ANIM_CLIP_REVEAL) {
a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
- change.getEndAbsBounds(), change.getEndAbsBounds(),
- options.getTransitionBounds());
+ endBounds, endBounds, options.getTransitionBounds());
} else if (overrideType == ANIM_SCALE_UP) {
a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
- change.getEndAbsBounds(), options.getTransitionBounds());
+ endBounds, options.getTransitionBounds());
} else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
|| overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
- change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
+ endBounds, type, wallpaperTransit, options.getThumbnail(),
options.getTransitionBounds());
} else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
// This received a transferred starting window, so don't animate
return null;
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
- } else if (type == TRANSIT_OPEN) {
- if (isTask) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskOpenEnterAnimation
- : R.styleable.WindowAnimation_taskOpenExitAnimation);
- } else {
+ } else {
+ int animAttr = 0;
+ boolean translucent = false;
+ if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (type == TRANSIT_OPEN) {
+ // We will translucent open animation for translucent activities and tasks. Choose
+ // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
+ // TransitionAnimation loads appropriate animation later.
if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- R.anim.activity_translucent_open_enter);
+ translucent = true;
+ }
+ if (isTask && !translucent) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+ : R.styleable.WindowAnimation_taskOpenExitAnimation;
} else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ animAttr = enter
? R.styleable.WindowAnimation_activityOpenEnterAnimation
- : R.styleable.WindowAnimation_activityOpenExitAnimation);
+ : R.styleable.WindowAnimation_activityOpenExitAnimation;
}
- }
- } else if (type == TRANSIT_TO_FRONT) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
- : R.styleable.WindowAnimation_taskToFrontExitAnimation);
- } else if (type == TRANSIT_CLOSE) {
- if (isTask) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskCloseEnterAnimation
- : R.styleable.WindowAnimation_taskCloseExitAnimation);
- } else {
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- R.anim.activity_translucent_close_exit);
+ } else if (type == TRANSIT_TO_FRONT) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+ : R.styleable.WindowAnimation_taskToFrontExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ if (isTask) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+ : R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+ translucent = true;
+ }
+ animAttr = enter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
- : R.styleable.WindowAnimation_activityCloseExitAnimation);
+ : R.styleable.WindowAnimation_activityCloseExitAnimation;
+ }
+ } else if (type == TRANSIT_TO_BACK) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+ : R.styleable.WindowAnimation_taskToBackExitAnimation;
+ }
+
+ if (animAttr != 0) {
+ if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+ a = mTransitionAnimation
+ .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+ animAttr, translucent);
+ } else {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
}
- } else if (type == TRANSIT_TO_BACK) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskToBackEnterAnimation
- : R.styleable.WindowAnimation_taskToBackExitAnimation);
}
if (a != null) {
if (!a.isInitialized()) {
- Rect end = change.getEndAbsBounds();
- a.initialize(end.width(), end.height(), end.width(), end.height());
+ final int width = endBounds.width();
+ final int height = endBounds.height();
+ a.initialize(width, height, width, height);
}
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
@@ -508,7 +807,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@NonNull Animation anim, @NonNull SurfaceControl leash,
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
- @Nullable Point position) {
+ @Nullable Point position, float cornerRadius, @Nullable Rect clipRect) {
final SurfaceControl.Transaction transaction = pool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
@@ -520,12 +819,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
- position);
+ position, cornerRadius, clipRect);
});
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
- position);
+ position, cornerRadius, clipRect);
pool.release(transaction);
mainExecutor.execute(() -> {
@@ -550,28 +849,30 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void attachThumbnail(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change,
- TransitionInfo.AnimationOptions options) {
+ TransitionInfo.AnimationOptions options, float cornerRadius) {
final boolean isTask = change.getTaskInfo() != null;
final boolean isOpen = Transitions.isOpeningType(change.getMode());
final boolean isClose = Transitions.isClosingType(change.getMode());
if (isOpen) {
if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
- attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
+ attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
+ cornerRadius);
} else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
- attachThumbnailAnimation(animations, finishCallback, change, options);
+ attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
}
} else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
- attachThumbnailAnimation(animations, finishCallback, change, options);
+ attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
}
}
- private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
- @NonNull Runnable finishCallback, TransitionInfo.Change change) {
- final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
- ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
+ private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
final Rect bounds = change.getEndAbsBounds();
+ // Show the right drawable depending on the user we're transitioning to.
+ final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
+ ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
- thumbnailDrawableRes, bounds);
+ thumbnailDrawable, bounds);
if (thumbnail == null) {
return;
}
@@ -594,12 +895,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top));
+ mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top),
+ cornerRadius, change.getEndAbsBounds());
}
private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change,
- TransitionInfo.AnimationOptions options) {
+ TransitionInfo.AnimationOptions options, float cornerRadius) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
change.getLeash(), options.getThumbnail(), transaction);
@@ -618,7 +920,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, null /* position */);
+ mMainExecutor, mAnimExecutor, null /* position */,
+ cornerRadius, change.getEndAbsBounds());
}
private static int getWallpaperTransitType(TransitionInfo info) {
@@ -650,13 +953,27 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static void applyTransformation(long time, SurfaceControl.Transaction t,
SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
- Point position) {
+ Point position, float cornerRadius, @Nullable Rect clipRect) {
anim.getTransformation(time, transformation);
if (position != null) {
transformation.getMatrix().postTranslate(position.x, position.y);
}
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
+
+ Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+ if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
+ // Clip out any overflowing edge extension
+ clipRect.inset(extensionInsets);
+ t.setCrop(leash, clipRect);
+ }
+
+ if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
+ // We can only apply rounded corner if a crop is set
+ t.setCrop(leash, clipRect);
+ t.setCornerRadius(leash, cornerRadius);
+ }
+
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
t.apply();
}
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 13c670a1ab1e..46f73fda37a1 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
@@ -19,6 +19,8 @@ package com.android.wm.shell.transition;
import static android.hardware.HardwareBuffer.RGBA_8888;
import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
import static android.util.RotationUtils.deltaRotation;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
import static com.android.wm.shell.transition.DefaultTransitionHandler.startSurfaceAnimation;
@@ -59,7 +61,7 @@ import java.util.Arrays;
* This class handles the rotation animation when the device is rotated.
*
* <p>
- * The screen rotation animation is composed of 4 different part:
+ * The screen rotation animation is composed of 3 different part:
* <ul>
* <li> The screenshot: <p>
* A screenshot of the whole screen prior the change of orientation is taken to hide the
@@ -75,10 +77,6 @@ import java.util.Arrays;
* To have the animation seem more seamless, we add a color transitioning background behind the
* exiting and entering layouts. We compute the brightness of the start and end
* layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- * The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- * rotation animation is used and matches the new content size instead of the screenshot.
* </ul>
*/
class ScreenRotationAnimation {
@@ -94,6 +92,7 @@ class ScreenRotationAnimation {
private final Rect mStartBounds = new Rect();
private final Rect mEndBounds = new Rect();
+ private final int mAnimHint;
private final int mStartWidth;
private final int mStartHeight;
private final int mEndWidth;
@@ -117,6 +116,7 @@ class ScreenRotationAnimation {
// rotations.
private Animation mRotateExitAnimation;
private Animation mRotateEnterAnimation;
+ private Animation mRotateAlphaAnimation;
/** Intensity of light/whiteness of the layout before rotation occurs. */
private float mStartLuma;
@@ -124,9 +124,10 @@ class ScreenRotationAnimation {
private float mEndLuma;
ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
- Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash) {
+ Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
mContext = context;
mTransactionPool = pool;
+ mAnimHint = animHint;
mSurfaceControl = change.getLeash();
mStartWidth = change.getStartAbsBounds().width();
@@ -160,13 +161,6 @@ class ScreenRotationAnimation {
return;
}
- mBackColorSurface = new SurfaceControl.Builder(session)
- .setParent(rootLeash)
- .setColorLayer()
- .setCallsite("ShellRotationAnimation")
- .setName("BackColorSurface")
- .build();
-
mScreenshotLayer = new SurfaceControl.Builder(session)
.setParent(mAnimLeash)
.setBLASTLayer()
@@ -175,17 +169,9 @@ class ScreenRotationAnimation {
.setName("RotationLayer")
.build();
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
-
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
screenshotBuffer.getHardwareBuffer());
- t.setLayer(mBackColorSurface, -1);
- t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
- t.show(mBackColorSurface);
-
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.setPosition(mAnimLeash, 0, 0);
t.setAlpha(mAnimLeash, 1);
@@ -195,6 +181,23 @@ class ScreenRotationAnimation {
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
t.show(mScreenshotLayer);
+ if (!isCustomRotate()) {
+ mBackColorSurface = new SurfaceControl.Builder(session)
+ .setParent(rootLeash)
+ .setColorLayer()
+ .setCallsite("ShellRotationAnimation")
+ .setName("BackColorSurface")
+ .build();
+
+ HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+ mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
+
+ t.setLayer(mBackColorSurface, -1);
+ t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
+ t.setAlpha(mBackColorSurface, 1);
+ t.show(mBackColorSurface);
+ }
+
} catch (Surface.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
@@ -203,6 +206,10 @@ class ScreenRotationAnimation {
t.apply();
}
+ private boolean isCustomRotate() {
+ return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
+ }
+
private void setRotation(SurfaceControl.Transaction t) {
// Compute the transformation matrix that must be applied
// to the snapshot to make it stay in the same original position
@@ -244,33 +251,44 @@ class ScreenRotationAnimation {
// color frame animation.
//mEndLuma = getLumaOfSurfaceControl(mEndBounds, mSurfaceControl);
- // Figure out how the screen has moved from the original rotation.
- int delta = deltaRotation(mEndRotation, mStartRotation);
- switch (delta) { /* Counter-Clockwise Rotations */
- case Surface.ROTATION_0:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.rotation_animation_enter);
- break;
- case Surface.ROTATION_90:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_enter);
- break;
- case Surface.ROTATION_180:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_enter);
- break;
- case Surface.ROTATION_270:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_enter);
- break;
+ final boolean customRotate = isCustomRotate();
+ if (customRotate) {
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+ : R.anim.rotation_animation_xfade_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.rotation_animation_enter);
+ mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_alpha);
+ } else {
+ // Figure out how the screen has moved from the original rotation.
+ int delta = deltaRotation(mEndRotation, mStartRotation);
+ switch (delta) { /* Counter-Clockwise Rotations */
+ case Surface.ROTATION_0:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_0_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.rotation_animation_enter);
+ break;
+ case Surface.ROTATION_90:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_plus_90_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_plus_90_enter);
+ break;
+ case Surface.ROTATION_180:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_180_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_180_enter);
+ break;
+ case Surface.ROTATION_270:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_minus_90_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_minus_90_enter);
+ break;
+ }
}
mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
@@ -281,9 +299,20 @@ class ScreenRotationAnimation {
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
mTransaction = mTransactionPool.acquire();
- startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
- startScreenshotRotationAnimation(animations, finishCallback, mainExecutor, animExecutor);
- //startColorAnimation(mTransaction, animationScale);
+ if (customRotate) {
+ mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
+ mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
+ mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+
+ startScreenshotAlphaAnimation(animations, finishCallback, mainExecutor,
+ animExecutor);
+ startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+ } else {
+ startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+ startScreenshotRotationAnimation(animations, finishCallback, mainExecutor,
+ animExecutor);
+ //startColorAnimation(mTransaction, animationScale);
+ }
return true;
}
@@ -292,14 +321,24 @@ class ScreenRotationAnimation {
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
startSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */);
+ mTransactionPool, mainExecutor, animExecutor, null /* position */,
+ 0 /* cornerRadius */, null /* clipRect */);
}
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
startSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */);
+ mTransactionPool, mainExecutor, animExecutor, null /* position */,
+ 0 /* cornerRadius */, null /* clipRect */);
+ }
+
+ private void startScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
+ @NonNull ShellExecutor animExecutor) {
+ startSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, animExecutor, null /* position */,
+ 0 /* cornerRadius */, null /* clipRect */);
}
private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
@@ -349,13 +388,12 @@ class ScreenRotationAnimation {
t.remove(mScreenshotLayer);
}
mScreenshotLayer = null;
-
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
+ }
+ if (mBackColorSurface != null) {
+ if (mBackColorSurface.isValid()) {
+ t.remove(mBackColorSurface);
}
+ mBackColorSurface = null;
}
t.apply();
mTransactionPool.release(t);
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 804e449decf8..435d67087f34 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
@@ -33,6 +33,7 @@ import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -72,34 +73,45 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.debug.shell_transit", false);
-
- /** Transition type for dismissing split-screen via dragging the divider off the screen. */
- public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1;
-
- /** Transition type for launching 2 tasks simultaneously. */
- public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
+ && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
/** Transition type for exiting PIP via the Shell, via pressing the expand button. */
- public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3;
+ public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1;
+
+ public static final int TRANSIT_EXIT_PIP_TO_SPLIT = TRANSIT_FIRST_CUSTOM + 2;
/** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
- public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
+ public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 3;
+
+ /** Transition type for launching 2 tasks simultaneously. */
+ public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 4;
/** Transition type for entering split by opening an app into side-stage. */
public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
+ /** Transition type for dismissing split-screen via dragging the divider off the screen. */
+ public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 6;
+
+ /** Transition type for dismissing split-screen. */
+ public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
private final RemoteTransitionHandler mRemoteTransitionHandler;
+ private final DisplayController mDisplayController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
+ /** List of {@link Runnable} instances to run when the last active transition has finished. */
+ private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
+
private float mTransitionAnimationScaleSetting = 1.0f;
private static final class ActiveTransition {
@@ -117,15 +129,17 @@ public class Transitions implements RemoteCallable<Transitions> {
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull DisplayController displayController, @NonNull Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
- animExecutor));
+ mainHandler, animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
@@ -147,6 +161,7 @@ public class Transitions implements RemoteCallable<Transitions> {
mContext = null;
mMainExecutor = null;
mAnimExecutor = null;
+ mDisplayController = null;
mPlayerImpl = null;
mRemoteTransitionHandler = null;
}
@@ -212,6 +227,21 @@ public class Transitions implements RemoteCallable<Transitions> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ /**
+ * Runs the given {@code runnable} when the last active transition has finished, or immediately
+ * if there are currently no active transitions.
+ *
+ * <p>This method should be called on the Shell main-thread, where the given {@code runnable}
+ * will be executed when the last active transition is finished.
+ */
+ public void runOnIdle(Runnable runnable) {
+ if (mActiveTransitions.isEmpty()) {
+ runnable.run();
+ } else {
+ mRunWhenIdleQueue.add(runnable);
+ }
+ }
+
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
@@ -351,6 +381,32 @@ public class Transitions implements RemoteCallable<Transitions> {
return;
}
+ // apply transfer starting window directly if there is no other task change. Since this
+ // is an activity->activity situation, we can detect it by selecting transitions with only
+ // 2 changes where neither are tasks and one is a starting-window recipient.
+ final int changeSize = info.getChanges().size();
+ if (changeSize == 2) {
+ boolean nonTaskChange = true;
+ boolean transferStartingWindow = false;
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null) {
+ nonTaskChange = false;
+ break;
+ }
+ if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+ transferStartingWindow = true;
+ }
+ }
+ if (nonTaskChange && transferStartingWindow) {
+ t.apply();
+ // Treat this as an abort since we are bypassing any merge logic and effectively
+ // finishing immediately.
+ onAbort(transitionToken);
+ return;
+ }
+ }
+
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mInfo = info;
active.mStartT = t;
@@ -482,6 +538,11 @@ public class Transitions implements RemoteCallable<Transitions> {
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
@@ -547,6 +608,17 @@ public class Transitions implements RemoteCallable<Transitions> {
break;
}
}
+ if (request.getDisplayChange() != null) {
+ TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
+ if (change.getEndRotation() != change.getStartRotation()) {
+ // Is a rotation, so dispatch to all displayChange listeners
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ mDisplayController.getChangeController().dispatchOnRotateDisplay(wct,
+ change.getDisplayId(), change.getStartRotation(), change.getEndRotation());
+ }
+ }
active.mToken = mOrganizer.startTransition(
request.getType(), transitionToken, wct);
mActiveTransitions.add(active);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
index b9b671635010..7e95814c06c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -16,16 +16,17 @@
package com.android.wm.shell.util;
+import android.graphics.Point;
+import android.util.RotationUtils;
import android.view.SurfaceControl;
-import java.util.ArrayList;
-
/**
- * Utility class that takes care of counter-rotating surfaces during a transition animation.
+ * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation
+ * during a transition animation. This gives the illusion that the child surfaces haven't rotated
+ * relative to the screen.
*/
public class CounterRotator {
- SurfaceControl mSurface = null;
- ArrayList<SurfaceControl> mRotateChildren = null;
+ private SurfaceControl mSurface = null;
/** Gets the surface with the counter-rotation. */
public SurfaceControl getSurface() {
@@ -36,52 +37,47 @@ public class CounterRotator {
* Sets up this rotator.
*
* @param rotateDelta is the forward rotation change (the rotation the display is making).
- * @param displayW (and H) Is the size of the rotating display.
+ * @param parentW (and H) Is the size of the rotating parent after the rotation.
*/
public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
- float displayW, float displayH) {
+ float parentW, float parentH) {
if (rotateDelta == 0) return;
- mRotateChildren = new ArrayList<>();
- // We want to counter-rotate, so subtract from 4
- rotateDelta = 4 - (rotateDelta + 4) % 4;
mSurface = new SurfaceControl.Builder()
.setName("Transition Unrotate")
.setContainerLayer()
.setParent(parent)
.build();
- // column-major
- if (rotateDelta == 1) {
- t.setMatrix(mSurface, 0, 1, -1, 0);
- t.setPosition(mSurface, displayW, 0);
- } else if (rotateDelta == 2) {
- t.setMatrix(mSurface, -1, 0, 0, -1);
- t.setPosition(mSurface, displayW, displayH);
- } else if (rotateDelta == 3) {
- t.setMatrix(mSurface, 0, -1, 1, 0);
- t.setPosition(mSurface, 0, displayH);
+ // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent
+ // already took). Child surfaces will be in the old rotation relative to the new parent
+ // rotation, so we need to forward-rotate the child surfaces to match.
+ RotationUtils.rotateSurface(t, mSurface, rotateDelta);
+ final Point tmpPt = new Point(0, 0);
+ // parentW/H are the size in the END rotation, the rotation utilities expect the starting
+ // size. So swap them if necessary
+ if ((rotateDelta % 2) != 0) {
+ final float w = parentW;
+ parentW = parentH;
+ parentH = w;
}
+ RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH);
+ t.setPosition(mSurface, tmpPt.x, tmpPt.y);
t.show(mSurface);
}
/**
- * Add a surface that needs to be counter-rotate.
+ * Adds a surface that needs to be counter-rotate.
*/
public void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
if (mSurface == null) return;
t.reparent(child, mSurface);
- mRotateChildren.add(child);
}
/**
- * Clean-up. This undoes any reparenting and effectively stops the counter-rotation.
+ * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
+ * counter rotation surface.
*/
- public void cleanUp(SurfaceControl rootLeash) {
+ public void cleanUp(SurfaceControl.Transaction finishTransaction) {
if (mSurface == null) return;
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
- t.reparent(mRotateChildren.get(i), rootLeash);
- }
- t.remove(mSurface);
- t.apply();
+ finishTransaction.remove(mSurface);
}
}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f49e80ae16b1..a244d14b0e14 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -2,3 +2,7 @@
# includes OWNERS from parent directories
natanieljr@google.com
pablogamito@google.com
+
+lbill@google.com
+madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index ad4ccc0288ad..574a9f4da627 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -16,10 +16,6 @@
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
</target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
- <!-- reboot the device to teardown any crashed tests -->
- <option name="cleanup-action" value="REBOOT" />
- </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="WMShellFlickerTests.apk"/>
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 c4be785cff19..cb478c84c2b7 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
@@ -17,11 +17,11 @@
@file:JvmName("CommonAssertions")
package com.android.wm.shell.flicker
-import android.graphics.Region
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.region.Region
fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd {
@@ -118,10 +118,10 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region(0, 0, displayBounds.bounds.right,
+ Region.from(0, 0, displayBounds.bounds.right,
dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
} else {
- Region(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
displayBounds.bounds.bottom)
}
}
@@ -129,10 +129,10 @@ fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right, displayBounds.bounds.bottom)
} else {
- Region(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
+ Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
displayBounds.bounds.right, displayBounds.bounds.bottom)
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
index b63d9fffdb61..4d87ec9e872f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
@@ -43,4 +43,4 @@ fun <R> waitForResult(
} while (SystemClock.uptimeMillis() - startTime < timeout)
return (false to null)
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index 038be9c190c2..b7c80df03ce2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -52,9 +52,9 @@ class AppPairsTestCannotPairNonResizeableApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
nonResizeableApp?.launchViaIntent(wmHelper)
// TODO pair apps through normal UX flow
@@ -80,7 +80,7 @@ class AppPairsTestCannotPairNonResizeableApps(
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index bbc6b2dbece8..1ac664eb1f62 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -46,9 +46,9 @@ import org.junit.runners.Parameterized
class AppPairsTestPairPrimaryAndSecondaryApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
// TODO pair apps through normal UX flow
executeShellCommand(
@@ -65,7 +65,7 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index bb784a809b7e..57bcbc093a62 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.apppairs
import android.platform.test.annotations.Presubmit
+import android.view.Display
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -24,6 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
@@ -52,15 +54,26 @@ class AppPairsTestSupportPairNonResizeableApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
nonResizeableApp?.launchViaIntent(wmHelper)
// TODO pair apps through normal UX flow
executeShellCommand(
composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
+ val waitConditions = mutableListOf(
+ WindowManagerConditionsFactory.isWindowVisible(primaryApp.component),
+ WindowManagerConditionsFactory.isLayerVisible(primaryApp.component),
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+
+ nonResizeableApp?.let {
+ waitConditions.add(
+ WindowManagerConditionsFactory.isWindowVisible(nonResizeableApp.component))
+ waitConditions.add(
+ WindowManagerConditionsFactory.isLayerVisible(nonResizeableApp.component))
+ }
+ wmHelper.waitFor(*waitConditions.toTypedArray())
}
}
@@ -84,7 +97,7 @@ class AppPairsTestSupportPairNonResizeableApps(
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index a1a4db112dfd..12910dd74271 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -47,9 +47,9 @@ import org.junit.runners.Parameterized
class AppPairsTestUnpairPrimaryAndSecondaryApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
setup {
eachRun {
executeShellCommand(
@@ -69,7 +69,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index 9e20bbbc1a1b..863c3aff63a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -25,14 +25,11 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -50,7 +47,6 @@ import org.junit.Test
abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val context: Context = instrumentation.context
- protected val isRotated = testSpec.config.startRotation.isRotated()
protected val activityHelper = ActivityHelper.getInstance()
protected val appPairsHelper = AppPairsHelper(instrumentation,
Components.SplitScreenActivity.LABEL,
@@ -82,20 +78,18 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
- transition(this, testSpec.config)
+ transition(this)
}
}
- internal open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
+ internal open val transition: FlickerBuilder.() -> Unit
+ get() = {
setup {
test {
device.wakeUpAndGoToHomeScreen()
}
eachRun {
- this.setRotation(configuration.startRotation)
+ this.setRotation(testSpec.startRotation)
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
nonResizeableApp?.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index 56a2531a3fe1..f2f4877a44c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
@@ -50,14 +49,14 @@ import org.junit.runners.Parameterized
class RotateTwoLaunchedAppsInAppPairsMode(
testSpec: FlickerTestParameter
) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
executeShellCommand(composePairsCommand(
primaryTaskId, secondaryTaskId, true /* pair */))
waitAppsShown(primaryApp, secondaryApp)
- setRotation(testSpec.config.endRotation)
+ setRotation(testSpec.endRotation)
}
}
@@ -85,15 +84,19 @@ class RotateTwoLaunchedAppsInAppPairsMode(
@Presubmit
@Test
fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
primaryApp.component)
- @FlakyTest
+ @Presubmit
@Test
fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
secondaryApp.component)
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index 0699a4fd0512..2a173d16004f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
@@ -50,11 +49,11 @@ import org.junit.runners.Parameterized
class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
testSpec: FlickerTestParameter
) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
- this.setRotation(testSpec.config.endRotation)
+ this.setRotation(testSpec.endRotation)
executeShellCommand(
composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
waitAppsShown(primaryApp, secondaryApp)
@@ -81,6 +80,10 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
@Test
override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun bothAppWindowsVisible() {
@@ -90,16 +93,16 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
}
}
- @FlakyTest(bugId = 172776659)
+ @Presubmit
@Test
fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
primaryApp.component)
- @FlakyTest(bugId = 172776659)
+ @Presubmit
@Test
fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
secondaryApp.component)
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
index b95193a17265..670fbd810907 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
@@ -34,7 +34,7 @@ abstract class RotateTwoLaunchedAppsTransition(
override val nonResizeableApp: SplitScreenHelper?
get() = null
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
test {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 322d8b5e4dac..278ba9b0f4db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -22,19 +22,17 @@ import android.app.NotificationManager
import android.content.Context
import android.os.ServiceManager
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.repetitions
import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
-import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -49,56 +47,47 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
protected val notifyManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE))
- protected val packageManager = context.getPackageManager()
- protected val uid = packageManager.getApplicationInfo(
+ protected val uid = context.packageManager.getApplicationInfo(
testApp.component.packageName, 0).uid
- protected lateinit var addBubbleBtn: UiObject2
- protected lateinit var cancelAllBtn: UiObject2
-
- protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ protected abstract val transition: FlickerBuilder.() -> Unit
@JvmOverloads
protected open fun buildTransition(
- extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
- ): FlickerBuilder.(Map<String, Any?>) -> Unit {
- return { configuration ->
-
+ extraSpec: FlickerBuilder.() -> Unit = {}
+ ): FlickerBuilder.() -> Unit {
+ return {
setup {
test {
notifyManager.setBubblesAllowed(testApp.component.packageName,
uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
testApp.launchViaIntent(wmHelper)
- addBubbleBtn = device.wait(Until.findObject(
- By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
- cancelAllBtn = device.wait(Until.findObject(
- By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
+ waitAndGetAddBubbleBtn()
+ waitAndGetCancelAllBtn()
}
}
teardown {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
+ test {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
- testApp.exit()
+ testApp.exit()
+ }
}
- extraSpec(this, configuration)
+ extraSpec(this)
}
}
- @FlakyTest
- @Test
- fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
- }
+ protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject(
+ By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+ protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject(
+ By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- repeat { testSpec.config.repetitions }
- transition(this, testSpec.config)
+ transition(this)
}
}
@@ -108,7 +97,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index bfdcb363a818..f6abc75037ed 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.bubble
import android.content.Context
import android.graphics.Point
+import android.platform.test.annotations.Presubmit
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.test.filters.RequiresDevice
@@ -27,7 +28,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -42,24 +47,38 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
-class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
- val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- val displaySize = DisplayMetrics()
+ private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private val displaySize = DisplayMetrics()
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
setup {
eachRun {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Add Bubble not found")
}
}
transitions {
- wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found")
+ wm.run { wm.getDefaultDisplay().getMetrics(displaySize) }
val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels)
val showBubble = device.wait(Until.findObject(
By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found")
}
}
+
+ @Presubmit
+ @Test
+ open fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt
new file mode 100644
index 000000000000..dd744b3c45ab
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt
@@ -0,0 +1,44 @@
+/*
+ * 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 androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class DismissBubbleScreenShellTransit(
+ testSpec: FlickerTestParameter
+) : DismissBubbleScreen(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 42eeadf3ddd9..2ec743c10413 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.bubble
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -23,7 +24,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -40,20 +45,33 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
-class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
setup {
test {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Add Bubble not found")
}
}
transitions {
val showBubble = device.wait(Until.findObject(
By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
showBubble?.run { showBubble.click() } ?: error("Bubble notify not found")
- device.pressBack()
}
}
+
+ @Presubmit
+ @Test
+ open fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt
new file mode 100644
index 000000000000..d92ec7781005
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class ExpandBubbleScreenShellTransit(
+ testSpec: FlickerTestParameter
+) : ExpandBubbleScreen(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
new file mode 100644
index 000000000000..fb404b913465
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:LaunchBubbleFromLockScreen`
+ *
+ * Actions:
+ * Launch an bubble from notification on lock screen
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
+ setup {
+ eachRun {
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Bubble widget not found")
+ device.sleep()
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ device.wakeUp()
+ }
+ }
+ transitions {
+ val notification = device.wait(Until.findObject(
+ By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
+ notification?.click() ?: error("Notification not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+ val showBubble = device.wait(Until.findObject(
+ By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
+ showBubble?.click() ?: error("Bubble notify not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+ val cancelAllBtn = waitAndGetCancelAllBtn()
+ cancelAllBtn?.click() ?: error("Cancel widget not found")
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun testAppIsVisibleAtEnd() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ testSpec.assertLayersEnd {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testAppIsVisibleAtEnd_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ testSpec.assertLayersEnd {
+ this.isVisible(testApp.component)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index 47e8c0c047a8..c43230e77683 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,11 +16,17 @@
package com.android.wm.shell.flicker.bubble
-import androidx.test.filters.RequiresDevice
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -37,12 +43,36 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
-class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
transitions {
- addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Bubble widget not found")
}
}
+
+ @Presubmit
+ @Test
+ open fun testAppIsAlwaysVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlakyTest(bugId = 218642026)
+ @Test
+ open fun testAppIsAlwaysVisible_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt
new file mode 100644
index 000000000000..9350868a99f4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class LaunchBubbleScreenShellTransit(
+ testSpec: FlickerTestParameter
+) : LaunchBubbleScreen(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index 194e28fd6e8a..8d1e315e2d5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -24,7 +25,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
import org.junit.runner.RunWith
+import org.junit.Test
import org.junit.runners.Parameterized
/**
@@ -39,13 +44,19 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
-class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition() {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
setup {
test {
for (i in 1..3) {
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
}
val showBubble = device.wait(Until.findObject(
@@ -63,4 +74,12 @@ class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test
}
}
}
+
+ @Presubmit
+ @Test
+ open fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
new file mode 100644
index 000000000000..ddebb6fed636
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class MultiBubblesScreenShellTransit(
+ testSpec: FlickerTestParameter
+) : MultiBubblesScreen(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index 623055f659b9..cf4ea467a29b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,25 +17,25 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.graphics.Region
import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.region.Region
class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
component: FlickerComponentName
) : BaseAppHelper(instrumentation, activityLabel, component) {
- fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region {
- val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
+ fun getPrimaryBounds(dividerBounds: Region): Region {
+ val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right,
dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
return primaryAppBounds
}
- fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region {
+ fun getSecondaryBounds(dividerBounds: Region): Region {
val displayBounds = WindowUtils.displayBounds
- val secondaryAppBounds = Region(0,
+ val secondaryAppBounds = Region.from(0,
dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
return secondaryAppBounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 57bc0d580d72..3dd9e0572947 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -61,7 +61,7 @@ abstract class BaseAppHelper(
private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
fun isShellTransitionsEnabled() =
- SystemProperties.getBoolean("persist.debug.shell_transit", false)
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false)
fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
try {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index 0f00edea136f..cc5b9f9eb26d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -62,7 +62,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
if (wmHelper == null) {
device.waitForIdle()
} else {
- require(wmHelper.waitImeShown()) { "IME did not appear" }
+ wmHelper.waitImeShown()
}
}
@@ -79,7 +79,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
if (wmHelper == null) {
uiDevice.waitForIdle()
} else {
- require(wmHelper.waitImeGone()) { "IME did did not close" }
+ wmHelper.waitImeGone()
}
} else {
// While pressing the back button should close the IME on TV as well, it may also lead
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 2357b0debb33..e9d438a569d5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.graphics.Rect
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
@@ -26,6 +25,7 @@ import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
@@ -58,17 +58,27 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
- /** {@inheritDoc} */
- override fun launchViaIntent(
+ /**
+ * Launches the app through an intent instead of interacting with the launcher and waits
+ * until the app window is in PIP mode
+ */
+ @JvmOverloads
+ fun launchViaIntentAndWaitForPip(
wmHelper: WindowManagerStateHelper,
- expectedWindowName: String,
- action: String?,
+ expectedWindowName: String = "",
+ action: String? = null,
stringExtras: Map<String, String>
) {
- super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
- wmHelper.waitFor("hasPipWindow") { it.wmState.hasPipWindow() }
+ launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras,
+ waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition))
}
+ /**
+ * Expand the PIP window back to full screen via intent and wait until the app is visible
+ */
+ fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
+ launchViaIntentAndWaitShown(wmHelper)
+
private fun focusOnObject(selector: BySelector): Boolean {
// We expect all the focusable UI elements to be arranged in a way so that it is possible
// to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
@@ -88,7 +98,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
clickObject(ENTER_PIP_BUTTON_ID)
// Wait on WMHelper or simply wait for 3 seconds
- wmHelper?.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000)
+ wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
// when entering pip, the dismiss button is visible at the start. to ensure the pip
// animation is complete, wait until the pip dismiss button is no longer visible.
// b/176822698: dismiss-only state will be removed in the future
@@ -148,7 +158,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
// Wait for animation to complete.
- wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
+ wmHelper.waitPipGone()
wmHelper.waitForHomeActivityVisible()
}
@@ -165,7 +175,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
- wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
+ wmHelper.waitPipGone()
wmHelper.waitForAppTransitionIdle()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index bd44d082a1aa..c86a1229d8d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -28,7 +28,6 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
@@ -53,9 +52,9 @@ import org.junit.runners.Parameterized
class EnterSplitScreenDockActivity(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
}
@@ -69,7 +68,7 @@ class EnterSplitScreenDockActivity(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
index 625d48b8ab5a..2f9244be9c18 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
@@ -48,9 +48,9 @@ class EnterSplitScreenFromDetachedRecentTask(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
index 2ed2806af528..1740c3ec24ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -27,7 +27,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
@@ -52,9 +51,9 @@ import org.junit.runners.Parameterized
class EnterSplitScreenLaunchToSide(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
@@ -69,13 +68,13 @@ class EnterSplitScreenLaunchToSide(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
index ee6cf341c9ff..4c063b918e96 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -55,9 +55,9 @@ class EnterSplitScreenNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
index 163b6ffda6e2..f75dee619564 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -54,9 +54,9 @@ class EnterSplitScreenSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index 2b629b0a7eb5..ef7d65e8a732 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -50,9 +49,9 @@ import org.junit.runners.Parameterized
class ExitLegacySplitScreenFromBottom(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
@@ -74,7 +73,7 @@ class ExitLegacySplitScreenFromBottom(
splitScreenApp.component, secondaryApp.component,
FlickerComponentName.SNAPSHOT)
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -94,11 +93,11 @@ class ExitLegacySplitScreenFromBottom(
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
- @Postsubmit
+ @FlakyTest
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
index 95fe3bef4852..d913a6d85d3d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -51,9 +51,9 @@ import org.junit.runners.Parameterized
class ExitPrimarySplitScreenShowSecondaryFullscreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
teardown {
eachRun {
secondaryApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
index f7d628d48769..f3ff7b156aaf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -53,9 +53,9 @@ class LegacySplitScreenFromIntentNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
index a5c6571f68de..42e707ab0850 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -53,9 +53,9 @@ class LegacySplitScreenFromIntentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
index 6f486b0ddfea..c1fba7d1530c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -55,9 +55,9 @@ class LegacySplitScreenFromRecentNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
@@ -124,7 +124,7 @@ class LegacySplitScreenFromRecentNotSupportNonResizable(
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
index f03c927b8d58..6ac8683ac054 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -54,9 +54,9 @@ class LegacySplitScreenFromRecentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
index 1e89a25c06df..b01f41c9e2ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
@@ -26,7 +26,7 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
abstract class LegacySplitScreenRotateTransition(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 2ccd03bf1d6a..fb1004bda0cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -16,16 +16,15 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
@@ -61,8 +60,8 @@ class LegacySplitScreenToLauncher(
) : LegacySplitScreenTransition(testSpec) {
private val testApp = SimpleAppHelper(instrumentation)
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
setup {
test {
device.wakeUpAndGoToHomeScreen()
@@ -70,7 +69,7 @@ class LegacySplitScreenToLauncher(
}
eachRun {
testApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.endRotation)
+ this.setRotation(testSpec.endRotation)
device.launchSplitScreen(wmHelper)
device.waitForIdle()
}
@@ -109,7 +108,7 @@ class LegacySplitScreenToLauncher(
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@@ -117,11 +116,11 @@ class LegacySplitScreenToLauncher(
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
- @Postsubmit
+ @FlakyTest
@Test
fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -131,7 +130,7 @@ class LegacySplitScreenToLauncher(
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index 661c8b69068e..a4a1f617e497 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -25,12 +25,9 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
@@ -45,7 +42,6 @@ import org.junit.Test
abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val context: Context = instrumentation.context
- protected val isRotated = testSpec.config.startRotation.isRotated()
protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
@@ -82,15 +78,15 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
- protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
+ protected open val transition: FlickerBuilder.() -> Unit
+ get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
splitScreenApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
teardown {
@@ -105,19 +101,17 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
- transition(this, testSpec.config)
+ transition(this)
}
}
- internal open val cleanSetup: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
+ internal open val cleanSetup: FlickerBuilder.() -> Unit
+ get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- this.setRotation(configuration.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
index 34eff80a04bc..087b21c544c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -49,9 +49,9 @@ import org.junit.runners.Parameterized
class OpenAppToLegacySplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
wmHelper.waitForAppTransitionIdle()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 58e1def6f37a..a510d699387e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.graphics.Region
import android.util.Rational
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -37,10 +36,10 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.region.Region
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
@@ -69,12 +68,12 @@ class ResizeLegacySplitScreen(
private val testAppTop = SimpleAppHelper(instrumentation)
private val testAppBottom = ImeAppHelper(instrumentation)
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
- this.setRotation(configuration.startRotation)
+ this.setRotation(testSpec.startRotation)
this.launcherStrategy.clearRecentAppsFromOverview()
testAppBottom.launchViaIntent(wmHelper)
device.pressHome()
@@ -134,6 +133,7 @@ class ResizeLegacySplitScreen(
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@@ -166,9 +166,9 @@ class ResizeLegacySplitScreen(
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
- val topAppBounds = Region(0, 0, dividerBounds.right,
+ val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
+ val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
@@ -187,9 +187,9 @@ class ResizeLegacySplitScreen(
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
- val topAppBounds = Region(0, 0, dividerBounds.right,
+ val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
+ val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
@@ -220,8 +220,8 @@ class ResizeLegacySplitScreen(
.map {
val description = (startRatio.toString().replace("/", "-") + "_to_" +
stopRatio.toString().replace("/", "-"))
- val newName = "${FlickerTestParameter.defaultName(it.config)}_$description"
- FlickerTestParameter(it.config, name = newName)
+ val newName = "${FlickerTestParameter.defaultName(it)}_$description"
+ FlickerTestParameter(it.config, nameOverride = newName)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 8a50bc0b20cf..d703ea082c87 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -29,7 +29,6 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -53,12 +52,12 @@ import org.junit.runners.Parameterized
class RotateOneLaunchedAppAndEnterSplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
- this.setRotation(testSpec.config.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
@@ -69,14 +68,14 @@ class RotateOneLaunchedAppAndEnterSplitScreen(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index 84676a9186be..6b1883914e59 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -29,7 +29,6 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -53,11 +52,11 @@ import org.junit.runners.Parameterized
class RotateOneLaunchedAppInSplitScreenMode(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
- this.setRotation(testSpec.config.startRotation)
+ this.setRotation(testSpec.startRotation)
device.launchSplitScreen(wmHelper)
}
}
@@ -69,13 +68,13 @@ class RotateOneLaunchedAppInSplitScreenMode(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
- testSpec.config.startRotation, splitScreenApp.component)
+ testSpec.startRotation, splitScreenApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index 2abdca9216f9..acd658b5ba56 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -29,7 +30,6 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -54,11 +54,11 @@ import org.junit.runners.Parameterized
class RotateTwoLaunchedAppAndEnterSplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
- this.setRotation(testSpec.config.startRotation)
+ this.setRotation(testSpec.startRotation)
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
}
@@ -71,20 +71,20 @@ class RotateTwoLaunchedAppAndEnterSplitScreen(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index fe9b9f514015..b40be8b5f401 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -30,7 +30,6 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -55,18 +54,18 @@ import org.junit.runners.Parameterized
class RotateTwoLaunchedAppInSplitScreenMode(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
setup {
eachRun {
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
- this.setRotation(testSpec.config.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
transitions {
- this.setRotation(testSpec.config.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
@@ -77,20 +76,20 @@ class RotateTwoLaunchedAppInSplitScreenMode(
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 52a744f3897d..274d34ba3c5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -26,7 +26,9 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -55,16 +57,35 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setupAndTeardown(this)
+ setup {
+ eachRun {
+ pipApp.launchViaIntent(wmHelper)
+ }
+ }
+ teardown {
+ eachRun {
+ pipApp.exit(wmHelper)
+ }
+ }
transitions {
pipApp.clickEnterPipButton(wmHelper)
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
/**
* Checks [pipApp] window remains visible throughout the animation
*/
@@ -94,8 +115,8 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -106,8 +127,8 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertLayersVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -153,13 +174,14 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
}
/**
- * Checks the focus doesn't change during the animation
+ * Checks that the focus changes between the [pipApp] window and the launcher when
+ * closing the pip window
*/
- @FlakyTest
+ @Presubmit
@Test
- fun focusDoesNotChange() {
+ fun focusChanges() {
testSpec.assertEventLog {
- this.focusDoesNotChange()
+ this.focusChanges(pipApp.`package`, "NexusLauncherActivity")
}
}
@@ -175,7 +197,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index c8c3f4d64294..accb524d3de1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -74,9 +74,9 @@ class EnterPipToOtherOrientationTest(
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- setupAndTeardown(this, configuration)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setupAndTeardown(this)
setup {
eachRun {
@@ -98,7 +98,7 @@ class EnterPipToOtherOrientationTest(
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- wmHelper.waitFor { it.wmState.hasPipWindow() }
+ wmHelper.waitPipShown()
wmHelper.waitForAppTransitionIdle()
// during rotation the status bar becomes invisible and reappears at the end
wmHelper.waitForNavBarStatusBarVisible()
@@ -117,7 +117,7 @@ class EnterPipToOtherOrientationTest(
* Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
* the start and end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@@ -224,7 +224,7 @@ class EnterPipToOtherOrientationTest(
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 64b7eb53bd6f..990872f58dc1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -34,8 +34,8 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -46,8 +46,8 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertLayersVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -102,7 +102,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
}
/**
- * Checks that the visible region of [pipApp] covers the full display area at the end of
+ * Checks that the visible region oft [pipApp] covers the full display area at the end of
* the transition
*/
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 5207fed59208..0b4bc761838d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -18,23 +18,22 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
import org.junit.Test
/**
* Base class for exiting pip (closing pip window) without returning to the app
*/
abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition(eachRun = true) { configuration ->
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition(eachRun = true) {
setup {
eachRun {
- this.setRotation(configuration.startRotation)
+ this.setRotation(testSpec.startRotation)
}
}
teardown {
@@ -52,11 +51,28 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
open fun pipWindowBecomesInvisible() {
- testSpec.assertWm {
- this.invoke("hasPipWindow") {
- it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component)
- }.then().invoke("!hasPipWindow") {
- it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component)
+ 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.
+ testSpec.assertWm {
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp.component)
+ .isAppWindowVisible(pipApp.component)
+ .isAppWindowOnTop(pipApp.component)
+ }.then().invoke("!hasPipWindow") {
+ it.isNotPinned(pipApp.component)
+ .isAppWindowNotOnTop(pipApp.component)
+ }
+ }
+ testSpec.assertWmEnd { isAppWindowInvisible(pipApp.component) }
+ } else {
+ testSpec.assertWm {
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component)
+ }.then().invoke("!hasPipWindow") {
+ it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component)
+ }
}
}
}
@@ -77,16 +93,4 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
.isVisible(LAUNCHER_COMPONENT)
}
}
-
- /**
- * Checks that the focus changes between the [pipApp] window and the launcher when
- * closing the pip window
- */
- @FlakyTest(bugId = 151179149)
- @Test
- open fun focusChanges() {
- testSpec.assertEventLog {
- this.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
- }
- }
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index b53342d6f2f7..e2d08346efb6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -24,6 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -52,6 +54,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
+@FlakyTest(bugId = 219750830)
class ExitPipViaExpandButtonClickTest(
testSpec: FlickerTestParameter
) : ExitPipToAppTransition(testSpec) {
@@ -59,7 +62,7 @@ class ExitPipViaExpandButtonClickTest(
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
@@ -71,10 +74,20 @@ class ExitPipViaExpandButtonClickTest(
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
+ wmHelper.waitForSurfaceAppeared(testApp.component)
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 197726610)
+ @Test
+ override fun pipLayerExpands() = super.pipLayerExpands()
+
companion object {
/**
* Creates the test configurations.
@@ -86,7 +99,7 @@ class ExitPipViaExpandButtonClickTest(
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 1fec3cf85214..3fe6f02eccf7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -17,13 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -52,11 +56,13 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
@@ -66,12 +72,22 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
}
transitions {
// This will bring PipApp to fullscreen
- pipApp.launchViaIntent(wmHelper)
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
+ wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 197726610)
+ @Test
+ override fun pipLayerExpands() = super.pipLayerExpands()
+
companion object {
/**
* Creates the test configurations.
@@ -83,7 +99,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 73626c23065a..9c095a2a039f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -16,14 +16,19 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -52,14 +57,34 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this, it)
+ super.transition(this)
transitions {
pipApp.closePipWindow(wmHelper)
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
+ /**
+ * Checks that the focus changes between the pip menu window and the launcher when clicking the
+ * dismiss button on pip menu to close the pip window.
+ */
+ @Presubmit
+ @Test
+ fun focusDoesNotChange() {
+ testSpec.assertEventLog {
+ this.focusChanges("PipMenuView", "NexusLauncherActivity")
+ }
+ }
+
companion object {
/**
* Creates the test configurations.
@@ -72,7 +97,7 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 9e43deef8d99..ab07ede5bb32 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -55,37 +55,21 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { args ->
- super.transition(this, args)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
transitions {
val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds
val pipCenterX = pipRegion.centerX()
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10)
- wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
+ wmHelper.waitPipGone()
wmHelper.waitForWindowSurfaceDisappeared(pipApp.component)
wmHelper.waitForAppTransitionIdle()
}
}
- @Presubmit
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Presubmit
- @Test
- override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
-
@FlakyTest
@Test
override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
@@ -94,17 +78,20 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+ /**
+ * Checks that the focus doesn't change between windows during the transition
+ */
@Presubmit
@Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
- @Presubmit
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+ fun focusDoesNotChange() {
+ testSpec.assertEventLog {
+ this.focusDoesNotChange()
+ }
+ }
companion object {
/**
@@ -118,7 +105,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 20)
+ repetitions = 3)
}
}
} \ No newline at end of file
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 d0fee9a82093..5fc80db6c617 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
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
+import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -55,7 +55,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = true) {
transitions {
pipApp.doubleClickPipWindow(wmHelper)
@@ -69,8 +69,8 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -81,8 +81,8 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertLayersVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -148,7 +148,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
/**
* Checks that the focus doesn't change between windows during the transition
*/
- @FlakyTest
+ @FlakyTest(bugId = 216306753)
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
@@ -156,6 +156,10 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
}
}
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
companion object {
/**
* Creates the test configurations.
@@ -168,7 +172,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 0ab857d755ee..87e927fd50ea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -17,14 +17,19 @@
package com.android.wm.shell.flicker.pip
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.traces.region.RegionSubject
+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
@@ -53,13 +58,18 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class MovePipDownShelfHeightChangeTest(
+open class MovePipDownShelfHeightChangeTest(
testSpec: FlickerTestParameter
) : MovePipShelfHeightTransition(testSpec) {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = false) {
teardown {
eachRun {
@@ -78,6 +88,11 @@ class MovePipDownShelfHeightChangeTest(
current.isHigherOrEqual(previous.region)
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
companion object {
/**
* Creates the test configurations.
@@ -89,7 +104,7 @@ class MovePipDownShelfHeightChangeTest(
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt
new file mode 100644
index 000000000000..0ff260b94dc8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip movement with Launcher shelf height change (decrease).
+ *
+ * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ *
+ * Actions:
+ * Launch [pipApp] in pip mode
+ * Launch [testApp]
+ * Press home
+ * Check if pip window moves down (visually)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+@FlakyTest(bugId = 219693385)
+class MovePipDownShelfHeightChangeTest_ShellTransit(
+ testSpec: FlickerTestParameter
+) : MovePipDownShelfHeightChangeTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 6e0324c17272..0499e7de9a0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Test
@@ -66,8 +66,8 @@ abstract class MovePipShelfHeightTransition(
@Presubmit
@Test
open fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -78,8 +78,8 @@ abstract class MovePipShelfHeightTransition(
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertLayersVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index e507edfda48c..388b5e0b5e47 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -16,15 +16,20 @@
package com.android.wm.shell.flicker.pip
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
import android.view.Surface
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.traces.region.RegionSubject
+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
@@ -56,10 +61,15 @@ import org.junit.runners.Parameterized
class MovePipUpShelfHeightChangeTest(
testSpec: FlickerTestParameter
) : MovePipShelfHeightTransition(testSpec) {
+ @Before
+ fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = false) {
teardown {
eachRun {
@@ -78,6 +88,11 @@ class MovePipUpShelfHeightChangeTest(
current.isLowerOrEqual(previous.region)
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
companion object {
/**
* Creates the test configurations.
@@ -89,7 +104,7 @@ class MovePipUpShelfHeightChangeTest(
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index aba8aced298f..1e30f6b83874 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -25,10 +26,12 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.ImeAppHelper
+import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,15 +47,21 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+@FlakyTest(bugId = 218604389)
+open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val imeApp = ImeAppHelper(instrumentation)
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition(eachRun = false) { configuration ->
+ @Before
+ open fun before() {
+ assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition(eachRun = false) {
setup {
test {
imeApp.launchViaIntent(wmHelper)
- setRotation(configuration.startRotation)
+ setRotation(testSpec.startRotation)
}
}
teardown {
@@ -71,15 +80,20 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
/**
* Ensure the pip window remains visible throughout any keyboard interactions
*/
@Presubmit
@Test
- fun pipInVisibleBounds() {
- testSpec.assertWm {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- coversAtMost(displayBounds, pipApp.component)
+ open fun pipInVisibleBounds() {
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ coversAtMost(displayBounds)
}
}
@@ -88,7 +102,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
*/
@Presubmit
@Test
- fun pipIsAboveAppWindow() {
+ open fun pipIsAboveAppWindow() {
testSpec.assertWmTag(TAG_IME_VISIBLE) {
isAboveWindow(FlickerComponentName.IME, pipApp.component)
}
@@ -102,7 +116,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
new file mode 100644
index 000000000000..1a21d32f568c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+
+ @FlakyTest(bugId = 214452854)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 9bea5c03dadb..21175a0767a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -27,7 +27,6 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.FixedAppHelper
@@ -64,10 +63,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
assumeFalse(isShellTransitionsEnabled())
}
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ override val transition: FlickerBuilder.() -> Unit
get() = {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
test {
removeAllTasksButHome()
@@ -92,11 +89,16 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@FlakyTest(bugId = 161435597)
@Test
fun pipWindowInsideDisplayBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertWmVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
@@ -113,8 +115,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
@FlakyTest(bugId = 161435597)
@Test
fun pipLayerInsideDisplayBounds() {
- testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.component)
+ testSpec.assertLayersVisibleRegion(pipApp.component) {
+ coversAtMost(displayBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 669f37ad1e72..c8ced1c9df12 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -25,14 +25,15 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +48,7 @@ import org.junit.runners.Parameterized
* Actions:
* Launch a [pipApp] in pip mode
* Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.config.startRotation] to [testSpec.config.endRotation]
+ * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
* (usually, 0->90 and 90->0)
*
* Notes:
@@ -63,23 +64,29 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+@FlakyTest(bugId = 218604389)
+open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val fixedApp = FixedAppHelper(instrumentation)
- private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
+ private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition(eachRun = false) { configuration ->
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition(eachRun = false) {
setup {
test {
fixedApp.launchViaIntent(wmHelper)
}
eachRun {
- setRotation(configuration.startRotation)
+ setRotation(testSpec.startRotation)
}
}
transitions {
- setRotation(configuration.endRotation)
+ setRotation(testSpec.endRotation)
}
}
@@ -100,7 +107,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
/**
* Checks the position of the status bar at the start and end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@@ -184,7 +191,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- repetitions = 5)
+ repetitions = 3)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
new file mode 100644
index 000000000000..a017f56af5bd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+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)
+@Group4
+@FlakyTest(bugId = 217777115)
+class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index e8a61e8a1dae..8d542c8ec9e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -26,15 +26,12 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -44,11 +41,10 @@ import org.junit.Test
abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val isRotated = testSpec.config.startRotation.isRotated()
protected val pipApp = PipAppHelper(instrumentation)
- protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+ protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
- protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ protected abstract val transition: FlickerBuilder.() -> Unit
// Helper class to process test actions by broadcast.
protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
private fun createIntentWithAction(broadcastAction: String): Intent {
@@ -60,13 +56,6 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
.sendBroadcast(createIntentWithAction(broadcastAction))
}
- fun requestOrientationForPip(orientation: Int) {
- instrumentation.context.sendBroadcast(
- createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION)
- .putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString())
- )
- }
-
companion object {
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
@JvmStatic
@@ -81,16 +70,14 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
- transition(this, testSpec.config)
+ transition(this)
}
}
/**
* Gets a configuration that handles basic setup and teardown of pip tests
*/
- protected val setupAndTeardown: FlickerBuilder.(Map<String, Any?>) -> Unit
+ protected val setupAndTeardown: FlickerBuilder.() -> Unit
get() = {
setup {
test {
@@ -121,23 +108,22 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
protected open fun buildTransition(
eachRun: Boolean,
stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
- extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
- ): FlickerBuilder.(Map<String, Any?>) -> Unit {
- return { configuration ->
- setupAndTeardown(this, configuration)
+ extraSpec: FlickerBuilder.() -> Unit = {}
+ ): FlickerBuilder.() -> Unit {
+ return {
+ setupAndTeardown(this)
setup {
test {
- removeAllTasksButHome()
if (!eachRun) {
- pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
- wmHelper.waitFor { it.wmState.hasPipWindow() }
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ wmHelper.waitPipShown()
}
}
eachRun {
if (eachRun) {
- pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
- wmHelper.waitFor { it.wmState.hasPipWindow() }
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ wmHelper.waitPipShown()
}
}
}
@@ -151,11 +137,10 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
if (!eachRun) {
pipApp.exit(wmHelper)
}
- removeAllTasksButHome()
}
}
- extraSpec(this, configuration)
+ extraSpec(this)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index d6dbc366aec0..f7384e742a04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,11 +25,16 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.testapp.Components
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import org.junit.Assert.assertEquals
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,75 +42,91 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ * Test exiting Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-class SetRequestedOrientationWhilePinnedTest(
+@FlakyTest(bugId = 218604389)
+open class SetRequestedOrientationWhilePinnedTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- setupAndTeardown(this, configuration)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ }
eachRun {
- // Launch the PiP activity fixed as landscape
+ // Launch the PiP activity fixed as landscape.
pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
- EXTRA_ENTER_PIP to "true"))
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+ // Enter PiP.
+ broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
+ wmHelper.waitPipShown()
+ wmHelper.waitForRotation(Surface.ROTATION_0)
+ wmHelper.waitForAppTransitionIdle()
+ // System bar may fade out during fixed rotation.
+ wmHelper.waitForNavBarStatusBarVisible()
}
}
teardown {
eachRun {
pipApp.exit(wmHelper)
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ removeAllTasksButHome()
}
}
transitions {
- // Request that the orientation is set to landscape
- broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
-
- // Launch the activity back into fullscreen and
- // ensure that it is now in landscape
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
pipApp.launchViaIntent(wmHelper)
wmHelper.waitForFullScreenApp(pipApp.component)
wmHelper.waitForRotation(Surface.ROTATION_90)
- assertEquals(Surface.ROTATION_90, device.displayRotation)
+ wmHelper.waitForAppTransitionIdle()
+ // System bar may fade out during fixed rotation.
+ wmHelper.waitForNavBarStatusBarVisible()
}
}
- @FlakyTest
+ @Presubmit
@Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+ fun displayEndsAt90Degrees() {
+ testSpec.assertWmEnd {
+ hasRotation(Surface.ROTATION_90)
+ }
+ }
- @FlakyTest
+ @Presubmit
@Test
- override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
- @FlakyTest
+ @Presubmit
@Test
override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
@FlakyTest
@Test
- override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
- @FlakyTest
+ @Presubmit
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
@@ -112,7 +134,7 @@ class SetRequestedOrientationWhilePinnedTest(
}
}
- @FlakyTest
+ @Presubmit
@Test
fun pipAppShowsOnTop() {
testSpec.assertWmEnd {
@@ -120,7 +142,7 @@ class SetRequestedOrientationWhilePinnedTest(
}
}
- @FlakyTest
+ @Presubmit
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
@@ -128,13 +150,15 @@ class SetRequestedOrientationWhilePinnedTest(
}
}
- @FlakyTest
+ @Presubmit
@Test
- fun pipAlwaysVisible() = testSpec.assertWm {
- this.isAppWindowVisible(pipApp.component)
+ fun pipAlwaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(pipApp.component)
+ }
}
- @FlakyTest
+ @Presubmit
@Test
fun pipAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
@@ -142,10 +166,6 @@ class SetRequestedOrientationWhilePinnedTest(
}
}
- @FlakyTest
- @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/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
new file mode 100644
index 000000000000..8d764a8d0e69
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test exiting Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTestShellTransit`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+@FlakyTest(bugId = 217777115)
+class SetRequestedOrientationWhilePinnedTestShellTransit(
+ testSpec: FlickerTestParameter
+) : SetRequestedOrientationWhilePinnedTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 31e9167c79b2..9c3b0fa183b6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -27,7 +27,7 @@ import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.pip.PipTestBase
import org.junit.After
import org.junit.Assert.assertFalse
-import org.junit.Assume
+import org.junit.Assume.assumeTrue
import org.junit.Before
abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATION_0) {
@@ -37,7 +37,7 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
@Before
final override fun televisionSetUp() {
// Should run only on TVs.
- Assume.assumeTrue(isTelevision)
+ assumeTrue(isTelevision)
systemUiProcessObserver.start()
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 2cdbffa7589c..bd98585b67ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -25,6 +25,7 @@
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:launchMode="singleTop"
+ android:theme="@style/CutoutShortEdges"
android:label="FixedApp"
android:exported="true">
<intent-filter>
@@ -37,6 +38,7 @@
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
+ android:theme="@style/CutoutShortEdges"
android:launchMode="singleTop"
android:label="PipApp"
android:exported="true">
@@ -52,6 +54,7 @@
<activity android:name=".ImeActivity"
android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
+ android:theme="@style/CutoutShortEdges"
android:label="ImeApp"
android:launchMode="singleTop"
android:exported="true">
@@ -68,6 +71,7 @@
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity"
+ android:theme="@style/CutoutShortEdges"
android:label="SplitScreenPrimaryApp"
android:exported="true">
<intent-filter>
@@ -79,6 +83,7 @@
<activity android:name=".SplitScreenSecondaryActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity"
+ android:theme="@style/CutoutShortEdges"
android:label="SplitScreenSecondaryApp"
android:exported="true">
<intent-filter>
@@ -90,6 +95,7 @@
<activity android:name=".NonResizeableActivity"
android:resizeableActivity="false"
android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
+ android:theme="@style/CutoutShortEdges"
android:label="NonResizeableApp"
android:exported="true">
<intent-filter>
@@ -100,6 +106,7 @@
<activity android:name=".SimpleActivity"
android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
+ android:theme="@style/CutoutShortEdges"
android:label="SimpleApp"
android:exported="true">
<intent-filter>
@@ -111,16 +118,19 @@
android:name=".LaunchBubbleActivity"
android:label="LaunchBubbleApp"
android:exported="true"
+ android:theme="@style/CutoutShortEdges"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".BubbleActivity"
android:label="BubbleApp"
android:exported="false"
+ android:theme="@style/CutoutShortEdges"
android:resizeableActivity="true" />
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
new file mode 100644
index 000000000000..23b51cc06f04
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<resources>
+ <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowBackground">@android:color/darker_gray</item>
+ </style>
+
+ <style name="CutoutDefault" parent="@style/DefaultTheme">
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
+ </style>
+
+ <style name="CutoutShortEdges" parent="@style/DefaultTheme">
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+
+ <style name="CutoutNever" parent="@style/DefaultTheme">
+ <item name="android:windowLayoutInDisplayCutoutMode">never</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
new file mode 100644
index 000000000000..8278c67a9b4f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflow;
+import com.android.wm.shell.bubbles.BubbleStackView;
+import com.android.wm.shell.bubbles.TestableBubblePositioner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.wm.shell.bubbles.BubbleOverflow}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubbleOverflowTest extends ShellTestCase {
+
+ private TestableBubblePositioner mPositioner;
+ private BubbleOverflow mOverflow;
+
+ @Mock
+ private BubbleController mBubbleController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class));
+ when(mBubbleController.getPositioner()).thenReturn(mPositioner);
+ when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class));
+
+ mOverflow = new BubbleOverflow(mContext, mPositioner);
+ }
+
+ @Test
+ public void test_initialize() {
+ assertThat(mOverflow.getExpandedView()).isNull();
+
+ mOverflow.initialize(mBubbleController);
+
+ assertThat(mOverflow.getExpandedView()).isNotNull();
+ assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
+ }
+
+ @Test
+ public void test_cleanUpExpandedState() {
+ mOverflow.createExpandedView();
+ assertThat(mOverflow.getExpandedView()).isNotNull();
+
+ mOverflow.cleanUpExpandedState();
+ assertThat(mOverflow.getExpandedView()).isNull();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index a3b98a8fc880..a6caefe6d3e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
@@ -334,8 +335,7 @@ public class ShellTaskOrganizerTests {
mOrganizer.onTaskAppeared(taskInfo1, null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- null /* taskConfig */, null /* taskListener */);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
clearInvocations(mCompatUI);
@@ -345,8 +345,7 @@ public class ShellTaskOrganizerTests {
taskInfo2.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- taskInfo1.configuration, taskListener);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
// Not show size compat UI if task is not visible.
clearInvocations(mCompatUI);
@@ -356,13 +355,121 @@ public class ShellTaskOrganizerTests {
taskInfo3.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- null /* taskConfig */, null /* taskListener */);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- null /* taskConfig */, null /* taskListener */);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ }
+
+ @Test
+ public void testOnEligibleForLetterboxEducationActivityChanged() {
+ final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ taskInfo1.displayId = DEFAULT_DISPLAY;
+ taskInfo1.topActivityEligibleForLetterboxEducation = false;
+ final TrackingTaskListener taskListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+
+ // Task listener sent to compat UI is null if top activity isn't eligible for letterbox
+ // education.
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+ // Task listener is non-null if top activity is eligible for letterbox education and task
+ // is visible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo2 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo2.displayId = taskInfo1.displayId;
+ taskInfo2.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+ // Task listener is null if task is invisible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo3 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo3.displayId = taskInfo1.displayId;
+ taskInfo3.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.isVisible = false;
+ mOrganizer.onTaskInfoChanged(taskInfo3);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+
+ clearInvocations(mCompatUI);
+ mOrganizer.onTaskVanished(taskInfo1);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ }
+
+ @Test
+ public void testOnCameraCompatActivityChanged() {
+ final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ taskInfo1.displayId = DEFAULT_DISPLAY;
+ taskInfo1.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+ final TrackingTaskListener taskListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+
+ // Task listener sent to compat UI is null if top activity doesn't request a camera
+ // compat control.
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+ // Task listener is non-null when request a camera compat control for a visible task.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo2 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo2.displayId = taskInfo1.displayId;
+ taskInfo2.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ taskInfo2.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+ // CompatUIController#onCompatInfoChanged is called when requested state for a camera
+ // compat control changes for a visible task.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo3 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo3.displayId = taskInfo1.displayId;
+ taskInfo3.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+ taskInfo3.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo3);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+
+ // CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
+ // mode for a visible task that has a compat control.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo4 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo4.displayId = taskInfo1.displayId;
+ taskInfo4.topActivityInSizeCompat = true;
+ taskInfo4.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+ taskInfo4.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo4);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+
+ // Task linster is null when a camera compat control is dimissed for a visible task.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo5 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo5.displayId = taskInfo1.displayId;
+ taskInfo5.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+ taskInfo5.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo5);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+
+ // Task linster is null when request a camera compat control for a invisible task.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo6 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo6.displayId = taskInfo1.displayId;
+ taskInfo6.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ taskInfo6.isVisible = false;
+ mOrganizer.onTaskInfoChanged(taskInfo6);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+
+ clearInvocations(mCompatUI);
+ mOrganizer.onTaskVanished(taskInfo1);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 6080f3ae78e8..403dbf9d9554 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -22,7 +22,7 @@ import android.content.Context;
import android.hardware.display.DisplayManager;
import android.testing.TestableContext;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
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/TaskViewTest.java
index 1cbad155ba7b..32f1587752cb 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/TaskViewTest.java
@@ -21,6 +21,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -37,18 +39,22 @@ import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceSession;
+import android.view.ViewTreeObserver;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
+import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
@@ -75,6 +81,8 @@ public class TaskViewTest extends ShellTestCase {
HandlerExecutor mExecutor;
@Mock
SyncTransactionQueue mSyncQueue;
+ @Mock
+ TaskViewTransitions mTaskViewTransitions;
SurfaceSession mSession;
SurfaceControl mLeash;
@@ -110,7 +118,7 @@ public class TaskViewTest extends ShellTestCase {
return null;
}).when(mSyncQueue).runInSync(any());
- mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue);
+ mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -123,7 +131,7 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetPendingListener_throwsException() {
- TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue);
+ TaskView taskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
taskView.setListener(mExecutor, mViewListener);
try {
taskView.setListener(mExecutor, mViewListener);
@@ -144,7 +152,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testOnTaskAppeared_noSurface() {
+ public void testOnTaskAppeared_noSurface_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
@@ -154,7 +163,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testOnTaskAppeared_withSurface() {
+ public void testOnTaskAppeared_withSurface_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
@@ -163,7 +173,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testSurfaceCreated_noTask() {
+ public void testSurfaceCreated_noTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
verify(mViewListener).onInitialized();
@@ -172,7 +183,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testSurfaceCreated_withTask() {
+ public void testSurfaceCreated_withTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -181,7 +193,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testSurfaceDestroyed_noTask() {
+ public void testSurfaceDestroyed_noTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
mTaskView.surfaceCreated(sh);
mTaskView.surfaceDestroyed(sh);
@@ -190,7 +203,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testSurfaceDestroyed_withTask() {
+ public void testSurfaceDestroyed_withTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(sh);
@@ -201,7 +215,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testOnReleased() {
+ public void testOnReleased_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
mTaskView.release();
@@ -211,7 +226,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testOnTaskVanished() {
+ public void testOnTaskVanished_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
mTaskView.onTaskVanished(mTaskInfo);
@@ -220,7 +236,8 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testOnBackPressedOnTaskRoot() {
+ public void testOnBackPressedOnTaskRoot_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
@@ -228,17 +245,199 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
- public void testSetOnBackPressedOnTaskRoot() {
+ public void testSetOnBackPressedOnTaskRoot_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
}
@Test
- public void testUnsetOnBackPressedOnTaskRoot() {
+ public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
mTaskView.onTaskVanished(mTaskInfo);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
}
+
+ @Test
+ public void testOnNewTask_noSurface() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+ verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+ verify(mViewListener, never()).onInitialized();
+ // If there's no surface the task should be made invisible
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+ }
+
+ @Test
+ public void testSurfaceCreated_noTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean());
+
+ verify(mViewListener).onInitialized();
+ // No task, no visibility change
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testOnNewTask_withSurface() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+ verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSurfaceCreated_withTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ verify(mViewListener).onInitialized();
+ verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(true));
+
+ mTaskView.prepareOpenAnimation(false /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
+ }
+
+ @Test
+ public void testSurfaceDestroyed_noTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskView.surfaceCreated(sh);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSurfaceDestroyed_withTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(false));
+
+ mTaskView.prepareHideAnimation(new SurfaceControl.Transaction());
+
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+ }
+
+ @Test
+ public void testOnReleased() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ mTaskView.release();
+
+ verify(mOrganizer).removeListener(eq(mTaskView));
+ verify(mViewListener).onReleased();
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
+ }
+
+ @Test
+ public void testOnTaskVanished() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ mTaskView.prepareCloseAnimation();
+
+ verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
+ }
+
+ @Test
+ public void testOnBackPressedOnTaskRoot() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+
+ verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
+ }
+
+ @Test
+ public void testSetOnBackPressedOnTaskRoot() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
+ }
+
+ @Test
+ public void testUnsetOnBackPressedOnTaskRoot() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
+
+ mTaskView.prepareCloseAnimation();
+ verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
+ }
+
+ @Test
+ public void testSetObscuredTouchRect() {
+ mTaskView.setObscuredTouchRect(
+ new Rect(/* left= */ 0, /* top= */ 10, /* right= */ 100, /* bottom= */ 120));
+ ViewTreeObserver.InternalInsetsInfo insetsInfo = new ViewTreeObserver.InternalInsetsInfo();
+ mTaskView.onComputeInternalInsets(insetsInfo);
+
+ assertThat(insetsInfo.touchableRegion.contains(0, 10)).isTrue();
+ // Region doesn't contain the right/bottom edge.
+ assertThat(insetsInfo.touchableRegion.contains(100 - 1, 120 - 1)).isTrue();
+
+ mTaskView.setObscuredTouchRect(null);
+ insetsInfo.touchableRegion.setEmpty();
+ mTaskView.onComputeInternalInsets(insetsInfo);
+
+ assertThat(insetsInfo.touchableRegion.contains(0, 10)).isFalse();
+ assertThat(insetsInfo.touchableRegion.contains(100 - 1, 120 - 1)).isFalse();
+ }
+
+ @Test
+ public void testSetObscuredTouchRegion() {
+ Region obscuredRegion = new Region(10, 10, 19, 19);
+ obscuredRegion.union(new Rect(30, 30, 39, 39));
+
+ mTaskView.setObscuredTouchRegion(obscuredRegion);
+ ViewTreeObserver.InternalInsetsInfo insetsInfo = new ViewTreeObserver.InternalInsetsInfo();
+ mTaskView.onComputeInternalInsets(insetsInfo);
+
+ assertThat(insetsInfo.touchableRegion.contains(10, 10)).isTrue();
+ assertThat(insetsInfo.touchableRegion.contains(20, 20)).isFalse();
+ assertThat(insetsInfo.touchableRegion.contains(30, 30)).isTrue();
+
+ mTaskView.setObscuredTouchRegion(null);
+ insetsInfo.touchableRegion.setEmpty();
+ mTaskView.onComputeInternalInsets(insetsInfo);
+
+ assertThat(insetsInfo.touchableRegion.contains(10, 10)).isFalse();
+ assertThat(insetsInfo.touchableRegion.contains(20, 20)).isFalse();
+ assertThat(insetsInfo.touchableRegion.contains(30, 30)).isFalse();
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 2b5cd601b200..51eec27cfc0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -18,6 +18,7 @@ package com.android.wm.shell;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -36,6 +37,7 @@ public final class TestRunningTaskInfoBuilder {
private WindowContainerToken mToken = createMockWCToken();
private int mParentTaskId = INVALID_TASK_ID;
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
+ private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
public static WindowContainerToken createMockWCToken() {
final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -60,6 +62,12 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ public TestRunningTaskInfoBuilder setWindowingMode(
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.parentTaskId = INVALID_TASK_ID;
@@ -67,6 +75,7 @@ public final class TestRunningTaskInfoBuilder {
info.parentTaskId = mParentTaskId;
info.configuration.windowConfiguration.setBounds(mBounds);
info.configuration.windowConfiguration.setActivityType(mActivityType);
+ info.configuration.windowConfiguration.setWindowingMode(mWindowingMode);
info.token = mToken;
info.isResizeable = true;
info.supportsMultiWindow = true;
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
new file mode 100644
index 000000000000..05230a9417bb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.back;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityTaskManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.BackEvent;
+import android.window.BackNavigationInfo;
+import android.window.IOnBackInvokedCallback;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest WMShellUnitTests:BackAnimationControllerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BackAnimationControllerTest {
+
+ private final ShellExecutor mShellExecutor = new TestShellExecutor();
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private SurfaceControl.Transaction mTransaction;
+
+ @Mock
+ private IActivityTaskManager mActivityTaskManager;
+
+ @Mock
+ private IOnBackInvokedCallback mIOnBackInvokedCallback;
+
+ private BackAnimationController mController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mController = new BackAnimationController(
+ mShellExecutor, mTransaction, mActivityTaskManager, mContext);
+ mController.setEnableAnimations(true);
+ }
+
+ private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
+ SurfaceControl screenshotSurface,
+ HardwareBuffer hardwareBuffer,
+ int backType) {
+ BackNavigationInfo navigationInfo = new BackNavigationInfo(
+ backType,
+ topAnimationTarget,
+ screenshotSurface,
+ hardwareBuffer,
+ new WindowConfiguration(),
+ new RemoteCallback((bundle) -> {}),
+ null);
+ try {
+ doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ RemoteAnimationTarget createAnimationTarget() {
+ SurfaceControl topWindowLeash = new SurfaceControl();
+ return new RemoteAnimationTarget(-1, RemoteAnimationTarget.MODE_CLOSING, topWindowLeash,
+ false, new Rect(), new Rect(), -1,
+ new Point(0, 0), new Rect(), new Rect(), new WindowConfiguration(),
+ true, null, null, null, false, -1);
+ }
+
+ @Test
+ @Ignore("b/207481538")
+ public void crossActivity_screenshotAttachedAndVisible() {
+ SurfaceControl screenshotSurface = new SurfaceControl();
+ HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
+ createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY);
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
+ verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
+ verify(mTransaction).setVisibility(screenshotSurface, true);
+ verify(mTransaction).apply();
+ }
+
+ @Test
+ public void crossActivity_surfaceMovesWithGesture() {
+ SurfaceControl screenshotSurface = new SurfaceControl();
+ HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
+ RemoteAnimationTarget animationTarget = createAnimationTarget();
+ createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY);
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
+ mController.onMotionEvent(
+ MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
+ BackEvent.EDGE_LEFT);
+ verify(mTransaction).setPosition(animationTarget.leash, 100, 100);
+ verify(mTransaction, atLeastOnce()).apply();
+ }
+
+ @Test
+ public void backToHome_dispatchesEvents() throws RemoteException {
+ mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
+ RemoteAnimationTarget animationTarget = createAnimationTarget();
+ createNavigationInfo(animationTarget, null, null,
+ BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ // Check that back start is dispatched.
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
+ verify(mIOnBackInvokedCallback).onBackStarted();
+
+ // Check that back progress is dispatched.
+ mController.onMotionEvent(
+ MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
+ BackEvent.EDGE_LEFT);
+ ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
+ assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+ mController.onMotionEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0),
+ BackEvent.EDGE_LEFT);
+ verify(mIOnBackInvokedCallback).onBackInvoked();
+ }
+}
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 8bc1223cfd64..169f03e7bc3e 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
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
+import android.content.LocusId;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
@@ -39,7 +40,6 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Log;
import android.util.Pair;
import android.view.WindowManager;
@@ -82,6 +82,7 @@ public class BubbleDataTest extends ShellTestCase {
private BubbleEntry mEntryC1;
private BubbleEntry mEntryInterruptive;
private BubbleEntry mEntryDismissed;
+ private BubbleEntry mEntryLocusId;
private Bubble mBubbleA1;
private Bubble mBubbleA2;
@@ -92,6 +93,7 @@ public class BubbleDataTest extends ShellTestCase {
private Bubble mBubbleC1;
private Bubble mBubbleInterruptive;
private Bubble mBubbleDismissed;
+ private Bubble mBubbleLocusId;
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
@@ -141,6 +143,10 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
mMainExecutor);
+ mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
+ new LocusId("locusId1"));
+ mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor);
+
mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
mMainExecutor);
mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
@@ -794,7 +800,7 @@ public class BubbleDataTest extends ShellTestCase {
}
@Test
- public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() {
+ public void test_expanded_removeLastBubble_collapsesIfOverflowNotEmpty() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
changeExpandedStateAtTime(true, 2000);
@@ -804,7 +810,7 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0);
- assertSelectionChangedTo(mBubbleData.getOverflow());
+ assertExpandedChangedTo(false);
}
@Test
@@ -939,6 +945,102 @@ public class BubbleDataTest extends ShellTestCase {
assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2);
}
+ /**
+ * There is one bubble in the stack. If a task matching the locusId becomes visible, suppress
+ * the bubble. If it is hidden, unsuppress the bubble.
+ */
+ @Test
+ public void test_onLocusVisibilityChanged_singleBubble() {
+ sendUpdatedEntryAtTime(mEntryLocusId, 1000);
+ mBubbleData.setListener(mListener);
+
+ // Suppress the bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+ verifyUpdateReceived();
+ assertBubbleSuppressed(mBubbleLocusId);
+ assertOrderNotChanged();
+ assertBubbleListContains(/* empty list */);
+
+ // Unsuppress the bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+ verifyUpdateReceived();
+ assertBubbleUnsuppressed(mBubbleLocusId);
+ assertOrderNotChanged();
+ assertBubbleListContains(mBubbleLocusId);
+ }
+
+ /**
+ * Bubble stack has multiple bubbles. Suppress bubble based on matching locusId. Suppressed
+ * bubble is at the top.
+ *
+ * When suppressed:
+ * - hide bubble
+ * - update order
+ * - update selection
+ *
+ * When unsuppressed:
+ * - show bubble
+ * - update order
+ * - update selection
+ */
+ @Test
+ public void test_onLocusVisibilityChanged_multipleBubbles_suppressTopBubble() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryLocusId, 3000);
+ mBubbleData.setListener(mListener);
+
+ // Suppress bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+ verifyUpdateReceived();
+ assertBubbleSuppressed(mBubbleLocusId);
+ assertSelectionChangedTo(mBubbleA2);
+ assertOrderChangedTo(mBubbleA2, mBubbleA1);
+
+ // Unsuppress bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+ verifyUpdateReceived();
+ assertBubbleUnsuppressed(mBubbleLocusId);
+ assertSelectionChangedTo(mBubbleLocusId);
+ assertOrderChangedTo(mBubbleLocusId, mBubbleA2, mBubbleA1);
+ }
+
+ /**
+ * Bubble stack has multiple bubbles. Suppress bubble based on matching locusId. Suppressed
+ * bubble is not at the top.
+ *
+ * When suppressed:
+ * - hide suppressed bubble
+ * - do not update order
+ * - do not update selection
+ *
+ * When unsuppressed:
+ * - show bubble
+ * - do not update order
+ * - do not update selection
+ */
+ @Test
+ public void test_onLocusVisibilityChanged_multipleBubbles_suppressStackedBubble() {
+ sendUpdatedEntryAtTime(mEntryLocusId, 1000);
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+ mBubbleData.setListener(mListener);
+
+ // Suppress bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+ verifyUpdateReceived();
+ assertBubbleSuppressed(mBubbleLocusId);
+ assertSelectionNotChanged();
+ assertBubbleListContains(mBubbleA2, mBubbleA1);
+
+ // Unsuppress bubble
+ mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+ verifyUpdateReceived();
+ assertBubbleUnsuppressed(mBubbleLocusId);
+ assertSelectionNotChanged();
+ assertBubbleListContains(mBubbleA2, mBubbleA1, mBubbleLocusId);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
@@ -995,9 +1097,29 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.overflowBubbles).isEqualTo(bubbles);
}
+ private void assertBubbleListContains(Bubble... bubbles) {
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertWithMessage("bubbleList").that(update.bubbles).containsExactlyElementsIn(bubbles);
+ }
+
+ private void assertBubbleSuppressed(Bubble expected) {
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertWithMessage("suppressedBubble").that(update.suppressedBubble).isEqualTo(expected);
+ }
+
+ private void assertBubbleUnsuppressed(Bubble expected) {
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertWithMessage("unsuppressedBubble").that(update.unsuppressedBubble).isEqualTo(expected);
+ }
+
private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
NotificationListenerService.Ranking ranking) {
- return createBubbleEntry(userId, notifKey, packageName, ranking, 1000);
+ return createBubbleEntry(userId, notifKey, packageName, ranking, 1000, null);
+ }
+
+ private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
+ NotificationListenerService.Ranking ranking, LocusId locusId) {
+ return createBubbleEntry(userId, notifKey, packageName, ranking, 1000, locusId);
}
private void setPostTime(BubbleEntry entry, long postTime) {
@@ -1010,15 +1132,18 @@ public class BubbleDataTest extends ShellTestCase {
* as a convenience to create a Notification w/BubbleMetadata.
*/
private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
- NotificationListenerService.Ranking ranking, long postTime) {
+ NotificationListenerService.Ranking ranking, long postTime,
+ LocusId locusId) {
// BubbleMetadata
Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder(
mExpandIntent, Icon.createWithResource("", 0))
.setDeleteIntent(mDeleteIntent)
+ .setSuppressableBubble(true)
.build();
// Notification -> BubbleMetadata
Notification notification = mock(Notification.class);
- notification.setBubbleMetadata(bubbleMetadata);
+ when(notification.getBubbleMetadata()).thenReturn(bubbleMetadata);
+ when(notification.getLocusId()).thenReturn(locusId);
// Notification -> extras
notification.extras = new Bundle();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 453050fcfab4..8b4e1f8bfdb7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.google.common.truth.Truth.assertThat;
@@ -24,11 +23,14 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -55,6 +58,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
+ @Mock WindowContainerTransaction mWct;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -80,10 +84,6 @@ public class SplitLayoutTests extends ShellTestCase {
// Verify it returns true if new config won't affect split layout.
assertThat(mSplitLayout.updateConfiguration(config)).isFalse();
- // Verify updateConfiguration returns true if the orientation changed.
- config.orientation = ORIENTATION_LANDSCAPE;
- assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
-
// Verify updateConfiguration returns true if it rotated.
config.windowConfiguration.setRotation(1);
assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
@@ -101,14 +101,21 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testSetDividePosition() {
- mSplitLayout.setDividePosition(anyInt());
+ mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+ verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
+
+ mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
}
@Test
public void testSetDivideRatio() {
+ mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
mSplitLayout.setDivideRatio(0.5f);
- verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+ mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@Test
@@ -141,6 +148,16 @@ public class SplitLayoutTests extends ShellTestCase {
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
}
+ @Test
+ public void testApplyTaskChanges_updatesSmallestScreenWidthDp() {
+ final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
+ final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
+ mSplitLayout.applyTaskChanges(mWct, task1, task2);
+
+ verify(mWct).setSmallestScreenWidthDp(eq(task1.token), anyInt());
+ verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
+ }
+
private void waitDividerFlingFinished() {
verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 9bb54a18063f..2e5078d86a8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -61,7 +61,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
public void testInitRelease() {
mSplitWindowManager.init(mSplitLayout, new InsetsState());
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
- mSplitWindowManager.release();
+ mSplitWindowManager.release(null /* t */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
}
}
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 f622edb7f134..596100dcdead 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
@@ -16,11 +16,14 @@
package com.android.wm.shell.compatui;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -29,6 +32,9 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
@@ -46,6 +52,8 @@ import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListen
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +63,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.Lazy;
+
/**
* Tests for {@link CompatUIController}.
*
@@ -75,7 +85,9 @@ public class CompatUIControllerTest extends ShellTestCase {
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
private @Mock SyncTransactionQueue mMockSyncQueue;
private @Mock ShellExecutor mMockExecutor;
- private @Mock CompatUIWindowManager mMockLayout;
+ private @Mock Lazy<Transitions> mMockTransitionsLazy;
+ private @Mock CompatUIWindowManager mMockCompatLayout;
+ private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -85,14 +97,27 @@ public class CompatUIControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
- doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
- doReturn(TASK_ID).when(mMockLayout).getTaskId();
+ doReturn(DISPLAY_ID).when(mMockCompatLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockCompatLayout).getTaskId();
+ doReturn(true).when(mMockCompatLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(DISPLAY_ID).when(mMockLetterboxEduLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
+ doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
mController = new CompatUIController(mContext, mMockDisplayController,
- mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
+ mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
+ mMockTransitionsLazy) {
+ @Override
+ CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockCompatLayout;
+ }
+
@Override
- CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
- Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
- return mMockLayout;
+ LetterboxEduWindowManager createLetterboxEduWindowManager(Context context,
+ TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockLetterboxEduLayout;
}
};
spyOn(mController);
@@ -106,27 +131,95 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
public void testOnCompatInfoChanged() {
- final Configuration taskConfig = new Configuration();
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+
+ // Verify that the compat controls are added with non-null task listener.
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+
+ // Verify that the compat controls and letterbox education are updated with new size compat
+ // info.
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
+
+ // Verify that compat controls and letterbox education are removed with null task listener.
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
+ /* taskListener= */ null);
+
+ verify(mMockCompatLayout).release();
+ verify(mMockLetterboxEduLayout).release();
+ }
+
+ @Test
+ public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
+ doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
+ doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+
+ // Verify that the layout is created again.
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+ }
+
+ @Test
+ public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
+ doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- // Verify that the restart button is added with non-null size compat info.
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
+ verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
- // Verify that the restart button is updated with non-null new size compat info.
- final Configuration newTaskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
- true /* show */);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
- // Verify that the restart button is removed with null size compat info.
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
+ // Verify that the layout is created again.
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).release();
+ verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
+
@Test
public void testOnDisplayAdded() {
mController.onDisplayAdded(DISPLAY_ID);
@@ -139,44 +232,45 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
mMockTaskListener);
mController.onDisplayRemoved(DISPLAY_ID + 1);
- verify(mMockLayout, never()).release();
+ verify(mMockCompatLayout, never()).release();
+ verify(mMockLetterboxEduLayout, never()).release();
verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID),
any());
mController.onDisplayRemoved(DISPLAY_ID);
verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any());
- verify(mMockLayout).release();
+ verify(mMockCompatLayout).release();
+ verify(mMockLetterboxEduLayout).release();
}
@Test
public void testOnDisplayConfigurationChanged() {
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
- mMockTaskListener);
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
- final Configuration newTaskConfig = new Configuration();
- mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+ mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
- verify(mMockLayout, never()).updateDisplayLayout(any());
+ verify(mMockCompatLayout, never()).updateDisplayLayout(any());
+ verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any());
- mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+ mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
- verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
}
@Test
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
- mMockTaskListener);
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
insetsSource.setFrame(0, 0, 1000, 1000);
@@ -186,101 +280,131 @@ public class CompatUIControllerTest extends ShellTestCase {
mOnInsetsChangedListenerCaptor.capture());
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(insetsState);
- verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
// No update if the insets state is the same.
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
- verify(mMockLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
}
@Test
- public void testChangeButtonVisibilityOnImeShowHide() {
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ public void testChangeLayoutsVisibilityOnImeShowHide() {
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
// Verify that the restart button is hidden after IME is showing.
- mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
- false /* show */);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
// Verify button is shown after IME is hidden.
- mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() {
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
- // Verify that the restart button is hidden after keyguard becomes occluded.
- mController.onKeyguardOccludedChanged(true);
+ // Verify that the restart button is hidden after keyguard becomes showing.
+ mController.onKeyguardShowingChanged(true);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
- // Verify button remains hidden while keyguard is occluded.
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ // Verify button remains hidden while keyguard is showing.
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
- false /* show */);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
- // Verify button is shown after keyguard becomes not occluded.
- mController.onKeyguardOccludedChanged(false);
+ // Verify button is shown after keyguard becomes not showing.
+ mController.onKeyguardShowingChanged(false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() {
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
- mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
- mController.onKeyguardOccludedChanged(true);
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
+ mController.onKeyguardShowingChanged(true);
- verify(mMockLayout, times(2)).updateVisibility(false);
+ verify(mMockCompatLayout, times(2)).updateVisibility(false);
+ verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
- // Verify button remains hidden after keyguard becomes not occluded since IME is showing.
- mController.onKeyguardOccludedChanged(false);
+ // Verify button remains hidden after keyguard becomes not showing since IME is showing.
+ mController.onKeyguardShowingChanged(false);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button is shown after IME is not showing.
- mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
- final Configuration taskConfig = new Configuration();
- mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() {
+ mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
+ mController.onKeyguardShowingChanged(true);
- mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
- mController.onKeyguardOccludedChanged(true);
+ verify(mMockCompatLayout, times(2)).updateVisibility(false);
+ verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
- verify(mMockLayout, times(2)).updateVisibility(false);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
- clearInvocations(mMockLayout);
+ // Verify button remains hidden after IME is hidden since keyguard is showing.
+ mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- // Verify button remains hidden after IME is hidden since keyguard is occluded.
- mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
- verify(mMockLayout).updateVisibility(false);
+ // Verify button is shown after keyguard becomes not showing.
+ mController.onKeyguardShowingChanged(false);
- // Verify button is shown after keyguard becomes not occluded.
- mController.onKeyguardOccludedChanged(false);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
+ }
- verify(mMockLayout).updateVisibility(true);
+ private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.displayId = displayId;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 2c3987bc358d..7d3e718313e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -16,13 +16,20 @@
package com.android.wm.shell.compatui;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import android.content.res.Configuration;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.SurfaceControlViewHost;
@@ -36,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import org.junit.Before;
import org.junit.Test;
@@ -61,32 +69,33 @@ public class CompatUILayoutTest extends ShellTestCase {
@Mock private SurfaceControlViewHost mViewHost;
private CompatUIWindowManager mWindowManager;
- private CompatUILayout mCompatUILayout;
+ private CompatUILayout mLayout;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
- mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
- false /* hasShownHint */);
+ mWindowManager = new CompatUIWindowManager(mContext,
+ createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
+ mSyncTransactionQueue, mCallback, mTaskListener,
+ new DisplayLayout(), new CompatUIHintsState());
- mCompatUILayout = (CompatUILayout)
+ mLayout = (CompatUILayout)
LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
- mCompatUILayout.inject(mWindowManager);
+ mLayout.inject(mWindowManager);
spyOn(mWindowManager);
- spyOn(mCompatUILayout);
+ spyOn(mLayout);
doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
}
@Test
public void testOnClickForRestartButton() {
- final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+ final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
verify(mWindowManager).onRestartButtonClicked();
- doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
}
@@ -94,7 +103,7 @@ public class CompatUILayoutTest extends ShellTestCase {
public void testOnLongClickForRestartButton() {
doNothing().when(mWindowManager).onRestartButtonLongClicked();
- final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+ final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performLongClick();
verify(mWindowManager).onRestartButtonLongClicked();
@@ -102,10 +111,101 @@ public class CompatUILayoutTest extends ShellTestCase {
@Test
public void testOnClickForSizeCompatHint() {
- mWindowManager.createLayout(true /* show */);
- final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
sizeCompatHint.performClick();
- verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
+ verify(mLayout).setSizeCompatHintVisibility(/* show= */ false);
+ }
+
+ @Test
+ public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final ImageButton button =
+ mLayout.findViewById(R.id.camera_compat_treatment_button);
+ button.performClick();
+
+ verify(mWindowManager).onCameraTreatmentButtonClicked();
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+ button.performClick();
+
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ }
+
+ @Test
+ public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final ImageButton button =
+ mLayout.findViewById(R.id.camera_compat_treatment_button);
+ button.performClick();
+
+ verify(mWindowManager).onCameraTreatmentButtonClicked();
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+ button.performClick();
+
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ }
+
+ @Test
+ public void testOnCameraDismissButtonClicked() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final ImageButton button =
+ mLayout.findViewById(R.id.camera_compat_dismiss_button);
+ button.performClick();
+
+ verify(mWindowManager).onCameraDismissButtonClicked();
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verify(mLayout).setCameraControlVisibility(/* show */ false);
+ }
+
+ @Test
+ public void testOnLongClickForCameraTreatmentButton() {
+ doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+ final ImageButton button =
+ mLayout.findViewById(R.id.camera_compat_treatment_button);
+ button.performLongClick();
+
+ verify(mWindowManager).onCameraButtonLongClicked();
+ }
+
+ @Test
+ public void testOnLongClickForCameraDismissButton() {
+ doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+ final ImageButton button = mLayout.findViewById(R.id.camera_compat_dismiss_button);
+ button.performLongClick();
+
+ verify(mWindowManager).onCameraButtonLongClicked();
+ }
+
+ @Test
+ public void testOnClickForCameraCompatHint() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final LinearLayout hint = mLayout.findViewById(R.id.camera_compat_hint);
+ hint.performClick();
+
+ verify(mLayout).setCameraCompatHintVisibility(/* show= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d5dcf2e11a46..e79b803b4304 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,21 +16,26 @@
package com.android.wm.shell.compatui;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.content.res.Configuration;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.view.DisplayInfo;
@@ -46,6 +51,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import org.junit.Before;
import org.junit.Test;
@@ -68,56 +74,107 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
- @Mock private CompatUILayout mCompatUILayout;
+ @Mock private CompatUILayout mLayout;
@Mock private SurfaceControlViewHost mViewHost;
- private Configuration mTaskConfig;
private CompatUIWindowManager mWindowManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTaskConfig = new Configuration();
- mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
- mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
- false /* hasShownHint */);
+ mWindowManager = new CompatUIWindowManager(mContext,
+ createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
+ mSyncTransactionQueue, mCallback, mTaskListener,
+ new DisplayLayout(), new CompatUIHintsState());
spyOn(mWindowManager);
- doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
}
@Test
public void testCreateSizeCompatButton() {
- // Not create layout if show is false.
- mWindowManager.createLayout(false /* show */);
+ // Doesn't create layout if show is false.
+ mWindowManager.mHasSizeCompat = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ false));
- verify(mWindowManager, never()).inflateCompatUILayout();
+ verify(mWindowManager, never()).inflateLayout();
- // Not create hint popup.
- mWindowManager.mShouldShowHint = false;
- mWindowManager.createLayout(true /* show */);
+ // Doesn't create hint popup.
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
- verify(mWindowManager).inflateCompatUILayout();
- verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+ verify(mLayout, never()).setSizeCompatHintVisibility(/* show= */ true);
- // Create hint popup.
+ // Creates hint popup.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ mWindowManager.release();
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = false;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ assertNotNull(mLayout);
+ verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+ verify(mLayout).setSizeCompatHintVisibility(/* show= */ true);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint);
+
+ // Returns false and doesn't create layout if has Size Compat is false.
+ clearInvocations(mWindowManager);
mWindowManager.release();
- mWindowManager.mShouldShowHint = true;
- mWindowManager.createLayout(true /* show */);
+ mWindowManager.mHasSizeCompat = false;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
- verify(mWindowManager, times(2)).inflateCompatUILayout();
- assertNotNull(mCompatUILayout);
- verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
- assertFalse(mWindowManager.mShouldShowHint);
+ verify(mWindowManager, never()).inflateLayout();
+ }
+
+ @Test
+ public void testCreateCameraCompatControl() {
+ // Doesn't create layout if show is false.
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Doesn't create hint popup.
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout).setCameraControlVisibility(/* show= */ true);
+ verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
+
+ // Creates hint popup.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ mWindowManager.release();
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = false;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ assertNotNull(mLayout);
+ verify(mLayout).setCameraControlVisibility(/* show= */ true);
+ verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint);
+
+ // Returns false and doesn't create layout if Camera Compat state is hidden
+ clearInvocations(mWindowManager);
+ mWindowManager.release();
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager, never()).inflateLayout();
}
@Test
public void testRelease() {
- mWindowManager.createLayout(true /* show */);
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.createLayout(/* canShow= */ true);
- verify(mWindowManager).inflateCompatUILayout();
+ verify(mWindowManager).inflateLayout();
mWindowManager.release();
@@ -126,11 +183,13 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateCompatInfo() {
- mWindowManager.createLayout(true /* show */);
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.createLayout(/* canShow= */ true);
// No diff
clearInvocations(mWindowManager);
- mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */);
+ TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
verify(mWindowManager, never()).release();
@@ -140,20 +199,100 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
clearInvocations(mWindowManager);
final ShellTaskOrganizer.TaskListener newTaskListener = mock(
ShellTaskOrganizer.TaskListener.class);
- mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
- true /* show */);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
verify(mWindowManager).release();
- verify(mWindowManager).createLayout(anyBoolean());
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Change Camera Compat state, show a control.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mLayout).setCameraControlVisibility(/* show= */ true);
+ verify(mLayout).updateCameraTreatmentButton(
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+ // Change Camera Compat state, update a control.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mLayout).setCameraControlVisibility(/* show= */ true);
+ verify(mLayout).updateCameraTreatmentButton(
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+ // Change has Size Compat to false, hides restart button.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mLayout).setRestartButtonVisibility(/* show= */ false);
+
+ // Change has Size Compat to true, shows restart button.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+
+ // Change Camera Compat state to dismissed, hide a control.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_DISMISSED);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mLayout).setCameraControlVisibility(/* show= */ false);
// Change task bounds, update position.
clearInvocations(mWindowManager);
- final Configuration newTaskConfiguration = new Configuration();
- newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
- mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener,
- true /* show */);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
verify(mWindowManager).updateSurfacePosition();
+
+ // Change has Size Compat to false, release layout.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ assertFalse(
+ mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfoLayoutNotInflatedYet() {
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.createLayout(/* canShow= */ false);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
+ // inflated
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
}
@Test
@@ -200,25 +339,26 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateVisibility() {
// Create button if it is not created.
- mWindowManager.mCompatUILayout = null;
- mWindowManager.updateVisibility(true /* show */);
+ mWindowManager.mLayout = null;
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.updateVisibility(/* canShow= */ true);
- verify(mWindowManager).createLayout(true /* show */);
+ verify(mWindowManager).createLayout(/* canShow= */ true);
// Hide button.
clearInvocations(mWindowManager);
- doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
- mWindowManager.updateVisibility(false /* show */);
+ doReturn(View.VISIBLE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ false);
verify(mWindowManager, never()).createLayout(anyBoolean());
- verify(mCompatUILayout).setVisibility(View.GONE);
+ verify(mLayout).setVisibility(View.GONE);
// Show button.
- doReturn(View.GONE).when(mCompatUILayout).getVisibility();
- mWindowManager.updateVisibility(true /* show */);
+ doReturn(View.GONE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager, never()).createLayout(anyBoolean());
- verify(mCompatUILayout).setVisibility(View.VISIBLE);
+ verify(mLayout).setVisibility(View.VISIBLE);
}
@Test
@@ -230,6 +370,37 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ public void testOnCameraDismissButtonClicked() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ clearInvocations(mLayout);
+ mWindowManager.onCameraDismissButtonClicked();
+
+ verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verify(mLayout).setCameraControlVisibility(/* show= */ false);
+ }
+
+ @Test
+ public void testOnCameraTreatmentButtonClicked() {
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.createLayout(/* canShow= */ true);
+ clearInvocations(mLayout);
+ mWindowManager.onCameraTreatmentButtonClicked();
+
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verify(mLayout).updateCameraTreatmentButton(
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+ mWindowManager.onCameraTreatmentButtonClicked();
+
+ verify(mCallback).onCameraControlStateUpdated(
+ TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verify(mLayout).updateCameraTreatmentButton(
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ }
+
+ @Test
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
@@ -239,15 +410,39 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testOnRestartButtonLongClicked_showHint() {
// Not create hint popup.
- mWindowManager.mShouldShowHint = false;
- mWindowManager.createLayout(true /* show */);
+ mWindowManager.mHasSizeCompat = true;
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
+ mWindowManager.createLayout(/* canShow= */ true);
- verify(mWindowManager).inflateCompatUILayout();
- verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout, never()).setSizeCompatHintVisibility(/* show= */ true);
mWindowManager.onRestartButtonLongClicked();
- verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+ verify(mLayout).setSizeCompatHintVisibility(/* show= */ true);
}
+ @Test
+ public void testOnCameraControlLongClicked_showHint() {
+ // Not create hint popup.
+ mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
+
+ mWindowManager.onCameraButtonLongClicked();
+
+ verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
+ }
+
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+ @TaskInfo.CameraCompatControlState int cameraCompatControlState) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ return taskInfo;
+ }
}
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/letterboxedu/LetterboxEduDialogLayoutTest.java
new file mode 100644
index 000000000000..1dee88c43806
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.compatui.letterboxedu;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+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:LetterboxEduDialogLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class LetterboxEduDialogLayoutTest extends ShellTestCase {
+
+ @Mock
+ private Runnable mDismissCallback;
+
+ private LetterboxEduDialogLayout mLayout;
+ private View mDismissButton;
+ private View mDialogContainer;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ 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);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertEquals(mLayout.getDialogContainer(),
+ mLayout.findViewById(R.id.letterbox_education_dialog_container));
+ assertEquals(mLayout.getDialogTitle(),
+ mLayout.findViewById(R.id.letterbox_education_dialog_title));
+ assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground());
+ assertEquals(mLayout.getBackground().getAlpha(), 0);
+ }
+
+ @Test
+ public void testOnDismissButtonClicked() {
+ assertTrue(mDismissButton.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnBackgroundClicked() {
+ assertTrue(mLayout.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnDialogContainerClicked() {
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testSetDismissOnClickListenerNull() {
+ mLayout.setDismissOnClickListener(null);
+
+ assertFalse(mDismissButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertFalse(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+}
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/letterboxedu/LetterboxEduWindowManagerTest.java
new file mode 100644
index 000000000000..f3a8cf45b7f8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.eq;
+import static org.mockito.Mockito.clearInvocations;
+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.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+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.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxEduWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxEduWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+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 int TASK_ID = 1;
+
+ private static final int TASK_WIDTH = 200;
+ private static final int TASK_HEIGHT = 100;
+ private static final int DISPLAY_CUTOUT_TOP = 5;
+ private static final int DISPLAY_CUTOUT_BOTTOM = 10;
+ private static final int DISPLAY_CUTOUT_HORIZONTAL = 20;
+
+ @Captor
+ private ArgumentCaptor<WindowManager.LayoutParams> mWindowAttrsCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mEndCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mRunOnIdleCaptor;
+
+ @Mock private LetterboxEduAnimationController mAnimationController;
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private SurfaceControlViewHost mViewHost;
+ @Mock private Transitions mTransitions;
+ @Mock private Runnable mOnDismissCallback;
+
+ private SharedPreferences mSharedPreferences;
+ @Nullable
+ private Boolean mInitialPrefValue1 = null;
+ @Nullable
+ private Boolean mInitialPrefValue2 = null;
+
+ @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();
+ }
+ }
+
+ @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();
+ }
+
+ @Test
+ public void testCreateLayout_notEligible_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+ true, USER_ID_1, /* isTaskbarEduShowing= */ true);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ false));
+
+ assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+ verify(mViewHost).setView(eq(layout), mWindowAttrsCaptor.capture());
+ verifyLayout(layout, mWindowAttrsCaptor.getValue(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */ DISPLAY_CUTOUT_TOP,
+ /* expectedExtraBottomMargin= */ DISPLAY_CUTOUT_BOTTOM);
+ View dialogTitle = layout.getDialogTitle();
+ assertNotNull(dialogTitle);
+ spyOn(dialogTitle);
+
+ // The education shouldn't be marked as seen until enter animation is done.
+ assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ // Clicking the layout does nothing until enter animation is done.
+ layout.performClick();
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+ // The dialog title shouldn't be focused for Accessibility until enter animation is done.
+ verify(dialogTitle, never()).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ verifyAndFinishEnterAnimation(layout);
+
+ assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ verify(dialogTitle).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ // Exit animation should start following a click on the layout.
+ layout.performClick();
+
+ // Window manager isn't released until exit animation is done.
+ verify(windowManager, never()).release();
+
+ // Verify multiple clicks are ignored.
+ layout.performClick();
+
+ verifyAndFinishExitAnimation(layout);
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback).run();
+ }
+
+ @Test
+ public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ false);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+ assertNotNull(windowManager.mLayout);
+ verifyAndFinishEnterAnimation(windowManager.mLayout);
+ assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+
+ windowManager.release();
+ windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ false);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+ assertNull(windowManager.mLayout);
+
+ clearInvocations(mTransitions, mAnimationController);
+
+ windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_2, /* isTaskbarEduShowing= */ false);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+ assertNotNull(windowManager.mLayout);
+ verifyAndFinishEnterAnimation(windowManager.mLayout);
+ assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ }
+
+ @Test
+ public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ assertNotNull(windowManager.mLayout);
+
+ verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture());
+
+ windowManager.release();
+
+ mRunOnIdleCaptor.getValue().run();
+
+ verify(mAnimationController, never()).startEnterAnimation(any(), any());
+ assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ }
+
+ @Test
+ public void testUpdateCompatInfo_updatesLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ assertTrue(windowManager.updateCompatInfo(
+ createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)),
+ mTaskListener, /* canShow= */ true));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100,
+ /* expectedHeight= */ 50, /* expectedExtraTopMargin= */ 0,
+ /* expectedExtraBottomMargin= */ 0);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+
+ // Window manager should be released (without animation) when eligible becomes false.
+ assertFalse(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ false),
+ mTaskListener, /* canShow= */ true));
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback, never()).run();
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+ assertNull(windowManager.mLayout);
+
+ assertTrue(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ true),
+ mTaskListener, /* canShow= */ true));
+
+ assertNotNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testUpdateCompatInfo_canShowFalse_doesNothing() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ false));
+ assertNull(windowManager.mLayout);
+
+ assertTrue(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ true),
+ mTaskListener, /* canShow= */ false));
+
+ assertNull(windowManager.mLayout);
+ verify(mViewHost, never()).relayout(any());
+ }
+
+ @Test
+ public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ int newDisplayCutoutTop = DISPLAY_CUTOUT_TOP + 7;
+ int newDisplayCutoutBottom = DISPLAY_CUTOUT_BOTTOM + 9;
+ windowManager.updateDisplayLayout(createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutTop,
+ DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutBottom)));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */
+ newDisplayCutoutTop, /* expectedExtraBottomMargin= */ newDisplayCutoutBottom);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+ }
+
+ @Test
+ public void testRelease_animationIsCancelled() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ windowManager.release();
+
+ verify(mAnimationController).cancelAnimation();
+ }
+
+ private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
+ int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
+ int expectedExtraBottomMargin) {
+ assertThat(params.width).isEqualTo(expectedWidth);
+ assertThat(params.height).isEqualTo(expectedHeight);
+ MarginLayoutParams dialogParams =
+ (MarginLayoutParams) layout.getDialogContainer().getLayoutParams();
+ int verticalMargin = (int) mContext.getResources().getDimension(
+ R.dimen.letterbox_education_dialog_margin);
+ assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin);
+ assertThat(dialogParams.bottomMargin).isEqualTo(verticalMargin + expectedExtraBottomMargin);
+ }
+
+ private void verifyAndFinishEnterAnimation(LetterboxEduDialogLayout layout) {
+ verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture());
+
+ // startEnterAnimation isn't called until run-on-idle runnable is called.
+ verify(mAnimationController, never()).startEnterAnimation(any(), any());
+
+ mRunOnIdleCaptor.getValue().run();
+
+ verify(mAnimationController).startEnterAnimation(eq(layout), mEndCallbackCaptor.capture());
+ mEndCallbackCaptor.getValue().run();
+ }
+
+ private void verifyAndFinishExitAnimation(LetterboxEduDialogLayout layout) {
+ verify(mAnimationController).startExitAnimation(eq(layout), mEndCallbackCaptor.capture());
+ mEndCallbackCaptor.getValue().run();
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible) {
+ return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible,
+ int userId, boolean isTaskbarEduShowing) {
+ LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
+ createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
+ createDisplayLayout(), mTransitions, mOnDismissCallback,
+ mAnimationController);
+
+ spyOn(windowManager);
+ doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
+ doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
+
+ return windowManager;
+ }
+
+ private DisplayLayout createDisplayLayout() {
+ return createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, DISPLAY_CUTOUT_TOP, DISPLAY_CUTOUT_HORIZONTAL,
+ DISPLAY_CUTOUT_BOTTOM));
+ }
+
+ private DisplayLayout createDisplayLayout(Insets insets) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = TASK_WIDTH;
+ displayInfo.logicalHeight = TASK_HEIGHT;
+ displayInfo.displayCutout = new DisplayCutout(
+ insets, null, null, null, null);
+ return new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligible) {
+ return createTaskInfo(eligible, USER_ID_1);
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligible, int userId) {
+ return createTaskInfo(eligible, userId, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligible, int userId, Rect bounds) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.userId = userId;
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityEligibleForLetterboxEducation = eligible;
+ taskInfo.configuration.windowConfiguration.setBounds(bounds);
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index fe66e225ad4a..bb6026c36c97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -19,11 +19,12 @@ package com.android.wm.shell.draganddrop;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -33,6 +34,7 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -51,9 +53,11 @@ import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
@@ -111,7 +115,6 @@ public class DragAndDropPolicyTest {
private ActivityManager.RunningTaskInfo mHomeTask;
private ActivityManager.RunningTaskInfo mFullscreenAppTask;
private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
- private ActivityManager.RunningTaskInfo mSplitPrimaryAppTask;
@Before
public void setUp() throws RemoteException {
@@ -144,8 +147,6 @@ public class DragAndDropPolicyTest {
mNonResizeableFullscreenAppTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
mNonResizeableFullscreenAppTask.isResizeable = false;
- mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- ACTIVITY_TYPE_STANDARD);
setRunningTask(mFullscreenAppTask);
}
@@ -181,6 +182,12 @@ public class DragAndDropPolicyTest {
info.configuration.windowConfiguration.setActivityType(actType);
info.configuration.windowConfiguration.setWindowingMode(winMode);
info.isResizeable = true;
+ info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
+ ".ActivityWithMode" + winMode);
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = info.baseActivity.getPackageName();
+ activityInfo.name = info.baseActivity.getClassName();
+ info.topActivityInfo = activityInfo;
return info;
}
@@ -256,6 +263,62 @@ public class DragAndDropPolicyTest {
}
}
+ @Test
+ public void testLaunchMultipleTask_differentActivity() {
+ setRunningTask(mFullscreenAppTask);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+ assertNull(fillInIntent);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_differentActivity_inSplitscreen() {
+ setRunningTask(mFullscreenAppTask);
+ doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+ doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+ assertNull(fillInIntent);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_sameActivity() {
+ setRunningTask(mFullscreenAppTask);
+
+ // Replace the mocked drag pending intent and ensure it resolves to the same activity
+ PendingIntent launchIntent = mock(PendingIntent.class);
+ ResolveInfo launchInfo = new ResolveInfo();
+ launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+ doReturn(Collections.singletonList(launchInfo))
+ .when(launchIntent).queryIntentComponents(anyInt());
+ mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+ launchIntent);
+
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+ assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+ }
+
+ @Test
+ public void testLaunchMultipleTask_sameActivity_inSplitScreen() {
+ setRunningTask(mFullscreenAppTask);
+
+ // Replace the mocked drag pending intent and ensure it resolves to the same activity
+ PendingIntent launchIntent = mock(PendingIntent.class);
+ ResolveInfo launchInfo = new ResolveInfo();
+ launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+ doReturn(Collections.singletonList(launchInfo))
+ .when(launchIntent).queryIntentComponents(anyInt());
+ mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+ launchIntent);
+
+ doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+ doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+ assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+ }
+
private Target filterTargetByType(ArrayList<Target> targets, int type) {
for (Target t : targets) {
if (type == t.type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
index 9cbdf1e2dbb6..4523e2c9cba5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.fullscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
@@ -30,6 +31,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.os.SystemProperties;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
@@ -47,6 +49,8 @@ import java.util.Optional;
@SmallTest
public class FullscreenTaskListenerTest {
+ private static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@Mock
private SyncTransactionQueue mSyncQueue;
@@ -71,6 +75,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testAnimatableTaskAppeared_notifiesUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0);
mListener.onTaskAppeared(info, mSurfaceControl);
@@ -80,6 +85,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0);
RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1);
@@ -93,6 +99,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
mListener.onTaskAppeared(info, mSurfaceControl);
@@ -102,6 +109,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
mListener.onTaskAppeared(info, mSurfaceControl);
@@ -112,6 +120,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
mListener.onTaskAppeared(info, mSurfaceControl);
@@ -122,6 +131,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testAnimatableTaskBecameInactive_notifiesUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0);
mListener.onTaskAppeared(animatableTask, mSurfaceControl);
RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0);
@@ -133,6 +143,7 @@ public class FullscreenTaskListenerTest {
@Test
public void testAnimatableTaskVanished_notifiesUnfoldController() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0);
mListener.onTaskAppeared(taskInfo, mSurfaceControl);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index f10dc16fae5c..b976c1287aca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -24,8 +24,8 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index 078e2b6cf574..16e92395c85e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -45,8 +45,8 @@ import android.window.DisplayAreaOrganizer;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
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
new file mode 100644
index 000000000000..ff6dfdb748c4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.kidsmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.startingsurface.StartingWindowController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KidsModeTaskOrganizerTest {
+ @Mock private ITaskOrganizerController mTaskOrganizerController;
+ @Mock private Context mContext;
+ @Mock private Handler mHandler;
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private ShellExecutor mTestExecutor;
+ @Mock private DisplayController mDisplayController;
+ @Mock private SurfaceControl mLeash;
+ @Mock private WindowContainerToken mToken;
+ @Mock private WindowContainerTransaction mTransaction;
+ @Mock private ForceShowNavigationBarSettingsObserver mObserver;
+ @Mock private StartingWindowController mStartingWindowController;
+ @Mock private DisplayInsetsController mDisplayInsetsController;
+
+ KidsModeTaskOrganizer mOrganizer;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ try {
+ doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
+ .when(mTaskOrganizerController).registerTaskOrganizer(any());
+ } catch (RemoteException e) {
+ }
+ // NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
+ mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
+ mHandler, mContext, mSyncTransactionQueue, mDisplayController,
+ mDisplayInsetsController, Optional.empty(), mObserver));
+ mOrganizer.initialize(mStartingWindowController);
+ doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
+ doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testKidsModeOn() {
+ doReturn(true).when(mObserver).isEnabled();
+
+ mOrganizer.updateKidsModeState();
+
+ verify(mOrganizer, times(1)).enable();
+ verify(mOrganizer, times(1)).registerOrganizer();
+ verify(mOrganizer, times(1)).createRootTask(
+ eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+
+ final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
+ WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
+ mOrganizer.onTaskAppeared(rootTask, mLeash);
+
+ assertThat(mOrganizer.mLaunchRootLeash).isEqualTo(mLeash);
+ assertThat(mOrganizer.mLaunchRootTask).isEqualTo(rootTask);
+ }
+
+ @Test
+ public void testKidsModeOff() {
+ doReturn(true).when(mObserver).isEnabled();
+ mOrganizer.updateKidsModeState();
+ final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
+ WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
+ mOrganizer.onTaskAppeared(rootTask, mLeash);
+
+ doReturn(false).when(mObserver).isEnabled();
+ mOrganizer.updateKidsModeState();
+
+
+ verify(mOrganizer, times(1)).disable();
+ verify(mOrganizer, times(1)).unregisterOrganizer();
+ verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+ assertThat(mOrganizer.mLaunchRootLeash).isNull();
+ assertThat(mOrganizer.mLaunchRootTask).isNull();
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(
+ int taskId, int windowingMode, IBinder cookies) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.token = mToken;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ final ArrayList<IBinder> launchCookies = new ArrayList<>();
+ if (cookies != null) {
+ launchCookies.add(cookies);
+ }
+ taskInfo.launchCookies = launchCookies;
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
new file mode 100644
index 000000000000..f3f70673b332
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.onehanded;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.TestableLooper;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link BackgroundWindowManager} */
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4.class)
+public class BackgroundWindowManagerTest extends ShellTestCase {
+ private BackgroundWindowManager mBackgroundWindowManager;
+ @Mock
+ private DisplayLayout mMockDisplayLayout;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+ mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testInitRelease() {
+ mBackgroundWindowManager.initView();
+ assertThat(mBackgroundWindowManager.getSurfaceControl()).isNotNull();
+
+ mBackgroundWindowManager.removeBackgroundLayer();
+ assertThat(mBackgroundWindowManager.getSurfaceControl()).isNull();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
deleted file mode 100644
index 7b9553c5ef9b..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
+++ /dev/null
@@ -1,131 +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.onehanded;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
-import android.window.IWindowContainerToken;
-import android.window.WindowContainerToken;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
- private DisplayAreaInfo mDisplayAreaInfo;
- private Display mDisplay;
- private DisplayLayout mDisplayLayout;
- private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer;
- private WindowContainerToken mToken;
- private SurfaceControl mLeash;
-
- @Mock
- IWindowContainerToken mMockRealToken;
- @Mock
- DisplayController mMockDisplayController;
- @Mock
- OneHandedSettingsUtil mMockSettingsUtil;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mToken = new WindowContainerToken(mMockRealToken);
- mLeash = new SurfaceControl();
- mDisplay = mContext.getDisplay();
- mDisplayLayout = new DisplayLayout(mContext, mDisplay);
- when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
- mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
- FEATURE_ONE_HANDED_BACKGROUND_PANEL);
-
- mSpiedBackgroundPanelOrganizer = spy(
- new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, mMockSettingsUtil,
- Runnable::run));
- mSpiedBackgroundPanelOrganizer.onDisplayChanged(mDisplayLayout);
- }
-
- @Test
- public void testOnDisplayAreaAppeared() {
- mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
- assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isTrue();
- verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
- }
-
- @Test
- public void testShowBackgroundLayer() {
- mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, null);
- mSpiedBackgroundPanelOrganizer.onStart();
-
- verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
- }
-
- @Test
- public void testRemoveBackgroundLayer() {
- mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
- assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isNotNull();
-
- reset(mSpiedBackgroundPanelOrganizer);
- mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer();
-
- assertThat(mSpiedBackgroundPanelOrganizer.mBackgroundSurface).isNull();
- }
-
- @Test
- public void testStateNone_onConfigurationChanged() {
- mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_NONE);
- mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
- verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
- }
-
- @Test
- public void testStateActivate_onConfigurationChanged() {
- mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_ACTIVE);
- mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
- verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 0a3a84923053..2886b97a3020 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -46,6 +46,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -72,8 +73,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Mock
DisplayController mMockDisplayController;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@Mock
OneHandedEventCallback mMockEventCallback;
@@ -86,6 +85,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
+ InteractionJankMonitor mMockJankMonitor;
+ @Mock
IOverlayManager mMockOverlayManager;
@Mock
TaskStackListenerImpl mMockTaskStackListener;
@@ -109,9 +110,9 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mSpiedTransitionState = spy(new OneHandedState());
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
+ when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null);
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
- when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
mDefaultEnabled);
when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -130,7 +131,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
mMockDisplayController,
- mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
mMockTutorialHandler,
@@ -138,6 +138,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil,
mSpiedTimeoutHandler,
mSpiedTransitionState,
+ mMockJankMonitor,
mMockUiEventLogger,
mMockOverlayManager,
mMockTaskStackListener,
@@ -153,6 +154,13 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testNullDisplayLayout() {
+ mSpiedOneHandedController.updateDisplayLayout(0);
+
+ verify(mMockDisplayAreaOrganizer, never()).setDisplayLayout(any());
+ }
+
+ @Test
public void testStartOneHandedShouldTriggerScheduleOffset() {
mSpiedTransitionState.setState(STATE_NONE);
mSpiedOneHandedController.setOneHandedEnabled(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index ef16fd391235..9c7f7237871a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -50,6 +50,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -94,11 +95,11 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
@Mock
WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
ShellExecutor mMockShellMainExecutor;
@Mock
OneHandedSettingsUtil mMockSettingsUitl;
+ @Mock
+ InteractionJankMonitor mJankMonitor;
List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>();
@@ -140,7 +141,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mMockSettingsUitl,
mMockAnimationController,
mTutorialHandler,
- mMockBackgroundOrganizer,
+ mJankMonitor,
mMockShellMainExecutor));
for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) {
@@ -427,9 +428,16 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mMockSettingsUitl,
mMockAnimationController,
mTutorialHandler,
- mMockBackgroundOrganizer,
+ mJankMonitor,
mMockShellMainExecutor));
assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse();
}
+
+ @Test
+ public void testDisplayArea_setDisplayLayout_should_updateDisplayBounds() {
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+
+ verify(mSpiedDisplayAreaOrganizer).updateDisplayBounds();
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index bea69c5d80ef..dba1b8b86261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -40,6 +40,7 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -66,8 +67,6 @@ public class OneHandedStateTest extends OneHandedTestCase {
@Mock
DisplayController mMockDisplayController;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@Mock
OneHandedTouchHandler mMockTouchHandler;
@@ -78,6 +77,8 @@ public class OneHandedStateTest extends OneHandedTestCase {
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
+ InteractionJankMonitor mMockJankMonitor;
+ @Mock
IOverlayManager mMockOverlayManager;
@Mock
TaskStackListenerImpl mMockTaskStackListener;
@@ -102,7 +103,6 @@ public class OneHandedStateTest extends OneHandedTestCase {
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
- when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
mDefaultEnabled);
when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -120,7 +120,6 @@ public class OneHandedStateTest extends OneHandedTestCase {
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
mMockDisplayController,
- mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
mMockTutorialHandler,
@@ -128,6 +127,7 @@ public class OneHandedStateTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil,
mSpiedTimeoutHandler,
mSpiedState,
+ mMockJankMonitor,
mMockUiEventLogger,
mMockOverlayManager,
mMockTaskStackListener,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index b1434ca325b7..63d8bfd1e7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -56,6 +56,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
OneHandedSettingsUtil mMockSettingsUtil;
@Mock
WindowManager mMockWindowManager;
+ @Mock
+ BackgroundWindowManager mMockBackgroundWindowManager;
@Before
public void setUp() {
@@ -63,10 +65,11 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0);
mDisplay = mContext.getDisplay();
- mDisplayLayout = new DisplayLayout(mContext, mDisplay);
+ mDisplayLayout = new DisplayLayout(getTestContext().getApplicationContext(), mDisplay);
mSpiedTransitionState = spy(new OneHandedState());
mSpiedTutorialHandler = spy(
- new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager));
+ new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager,
+ mMockBackgroundWindowManager));
mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
}
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 90f898aa09da..0059846c6055 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
@@ -29,6 +29,7 @@ import android.view.Gravity;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -72,16 +73,16 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private void initializeMockResources() {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(
- com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+ R.dimen.config_pictureInPictureDefaultAspectRatio,
DEFAULT_ASPECT_RATIO);
res.addOverride(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+ R.integer.config_defaultPictureInPictureGravity,
Gravity.END | Gravity.BOTTOM);
res.addOverride(
- com.android.internal.R.dimen.default_minimal_size_pip_resizable_task,
+ R.dimen.default_minimal_size_pip_resizable_task,
DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
- com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets,
+ R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
res.addOverride(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio,
@@ -107,7 +108,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
public void onConfigurationChanged_reloadResources() {
final float newDefaultAspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
final TestableResources res = mContext.getOrCreateTestableResources();
- res.addOverride(com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+ res.addOverride(R.dimen.config_pictureInPictureDefaultAspectRatio,
newDefaultAspectRatio);
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
@@ -463,7 +464,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private void overrideDefaultAspectRatio(float aspectRatio) {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(
- com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+ R.dimen.config_pictureInPictureDefaultAspectRatio,
aspectRatio);
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
@@ -471,7 +472,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private void overrideDefaultStackGravity(int stackGravity) {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+ R.integer.config_defaultPictureInPictureGravity,
stackGravity);
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
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 0172cf324eea..14d9fb9babc4 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
@@ -48,7 +48,6 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -76,7 +75,6 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
- @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen;
@Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
@@ -101,8 +99,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
mPipBoundsAlgorithm, mMockPhonePipMenuController,
mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
- mMockPipTransitionController, mMockOptionalLegacySplitScreen,
- mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger,
+ mMockPipTransitionController, mMockOptionalSplitScreen,
+ mMockDisplayController, mMockPipUiEventLogger,
mMockShellTaskOrganizer, mMainExecutor));
mMainExecutor.flushAll();
preparePipTaskOrg();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 935f6695538d..aef298ed478a 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
@@ -59,6 +59,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
+import java.util.Set;
/**
* Unit tests for {@link PipController}
@@ -209,4 +210,16 @@ public class PipControllerTest extends ShellTestCase {
verify(mMockPipMotionHelper, never()).movePip(any(Rect.class));
}
+
+ @Test
+ public void onKeepClearAreasChanged_updatesPipBoundsState() {
+ final int displayId = 1;
+ final Rect keepClearArea = new Rect(0, 0, 10, 10);
+ when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+
+ mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
+ displayId, Set.of(keepClearArea), Set.of());
+
+ verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
new file mode 100644
index 000000000000..e6ba70e1b60e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.util.Size
+import android.view.Gravity
+import org.junit.runner.RunWith
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
+import org.junit.Before
+import org.junit.Test
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNull
+
+@RunWith(AndroidTestingRunner::class)
+class TvPipKeepClearAlgorithmTest {
+ private val DEFAULT_PIP_SIZE = Size(384, 216)
+ private val EXPANDED_WIDE_PIP_SIZE = Size(384*2, 216)
+ private val DASHBOARD_WIDTH = 484
+ private val BOTTOM_SHEET_HEIGHT = 524
+ private val STASH_OFFSET = 64
+ private val PADDING = 16
+ private val SCREEN_SIZE = Size(1920, 1080)
+ private val SCREEN_EDGE_INSET = 50
+
+ private lateinit var pipSize: Size
+ private lateinit var movementBounds: Rect
+ private lateinit var algorithm: TvPipKeepClearAlgorithm
+ private var currentTime = 0L
+ private var restrictedAreas = mutableSetOf<Rect>()
+ private var unrestrictedAreas = mutableSetOf<Rect>()
+ private var gravity: Int = 0
+
+ @Before
+ fun setup() {
+ movementBounds = Rect(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height)
+ movementBounds.inset(SCREEN_EDGE_INSET, SCREEN_EDGE_INSET)
+
+ restrictedAreas.clear()
+ unrestrictedAreas.clear()
+ currentTime = 0L
+ pipSize = DEFAULT_PIP_SIZE
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ algorithm = TvPipKeepClearAlgorithm({ currentTime })
+ algorithm.setScreenSize(SCREEN_SIZE)
+ algorithm.setMovementBounds(movementBounds)
+ algorithm.pipAreaPadding = PADDING
+ algorithm.stashOffset = STASH_OFFSET
+ algorithm.stashDuration = 5000L
+ algorithm.setGravity(gravity)
+ algorithm.maxRestrictedDistanceFraction = 0.3
+ }
+
+ @Test
+ fun testAnchorPosition_BottomRight() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopRight() {
+ gravity = Gravity.TOP or Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopLeft() {
+ gravity = Gravity.TOP or Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_BottomLeft() {
+ gravity = Gravity.BOTTOM or Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Right() {
+ gravity = Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Left() {
+ gravity = Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Top() {
+ gravity = Gravity.TOP
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Bottom() {
+ gravity = Gravity.BOTTOM
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopCenterHorizontal() {
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_BottomCenterHorizontal() {
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_RightCenterVertical() {
+ gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_LeftCenterVertical() {
+ gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+ testAnchorPosition()
+ }
+
+ fun testAnchorPosition() {
+ val placement = getActualPlacement()
+
+ assertEquals(getExpectedAnchorBounds(), placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_KeepClearNotObstructing_StayAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.LEFT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = getExpectedAnchorBounds()
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedRightSidebar_PushedLeft() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorTopRight_UnrestrictedRightSidebar_PushedLeft() {
+ gravity = Gravity.TOP or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomLeft_UnrestrictedRightSidebar_StayAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.LEFT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ gravity = Gravity.BOTTOM
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun testExpanded_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ pipSize = EXPANDED_WIDE_PIP_SIZE
+ gravity = Gravity.BOTTOM
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_RestrictedSmallBottomBar_PushedUp() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(96)
+ restrictedAreas.add(bottomBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_RestrictedBottomSheet_StashDownAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ restrictedAreas.add(bottomBar)
+
+ val expectedBounds = getExpectedAnchorBounds()
+ expectedBounds.offsetTo(expectedBounds.left, SCREEN_SIZE.height - STASH_OFFSET)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_BOTTOM, placement.stashType)
+ assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_PushUp() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_RestrictedSidebar_StashAboveBottomSheet() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_UnrestrictedSidebar_PushUpLeft() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ unrestrictedAreas.add(sideBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(
+ SCREEN_EDGE_INSET - sideBar.width() - PADDING,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING
+ )
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsBecomeUnobstructed_Unstashes() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += 1000
+
+ restrictedAreas.remove(sideBar)
+ placement = getActualPlacement()
+ assertEquals(expectedUnstashBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += algorithm.stashDuration
+
+ placement = getActualPlacement()
+ assertEquals(expectedUnstashBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsObstructionChanges_UnstashTimeExtended() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += 1000
+
+ val newObstruction = Rect(
+ 0,
+ expectedUnstashBounds.top,
+ expectedUnstashBounds.right,
+ expectedUnstashBounds.bottom
+ )
+ restrictedAreas.add(newObstruction)
+
+ placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
+ }
+
+ private fun makeSideBar(width: Int, @Gravity.GravityFlags side: Int): Rect {
+ val sidebar = Rect(0, 0, width, SCREEN_SIZE.height)
+ if (side == Gravity.RIGHT) {
+ sidebar.offsetTo(SCREEN_SIZE.width - width, 0)
+ }
+ return sidebar
+ }
+
+ private fun makeBottomBar(height: Int): Rect {
+ return Rect(0, SCREEN_SIZE.height - height, SCREEN_SIZE.width, SCREEN_SIZE.height)
+ }
+
+ private fun getExpectedAnchorBounds(): Rect {
+ val expectedBounds = Rect()
+ Gravity.apply(gravity, pipSize.width, pipSize.height, movementBounds, expectedBounds)
+ return expectedBounds
+ }
+
+ private fun anchorBoundsOffsetBy(dx: Int, dy: Int): Rect {
+ val bounds = getExpectedAnchorBounds()
+ bounds.offset(dx, dy)
+ return bounds
+ }
+
+ private fun getActualPlacement(): Placement {
+ algorithm.setGravity(gravity)
+ return algorithm.calculatePipPosition(pipSize, restrictedAreas, unrestrictedAreas)
+ }
+
+ private fun assertNotStashed(actual: Placement) {
+ assertEquals(STASH_TYPE_NONE, actual.stashType)
+ assertNull(actual.unstashDestinationBounds)
+ assertEquals(0L, actual.unstashTime)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index c9720671f49c..0639ad5d0a62 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -67,8 +67,7 @@ public class MainStageTests extends ShellTestCase {
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
- true /* reparent */);
+ mMainStage.activate(mWct, true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index aab1e3a99c98..49f36a4be35f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -16,21 +16,19 @@
package com.android.wm.shell.splitscreen;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
-import android.window.IWindowContainerToken;
-import android.window.WindowContainerToken;
+import android.view.SurfaceSession;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -50,6 +48,7 @@ public class SplitTestUtils {
final SurfaceControl leash = createMockSurface();
SplitLayout out = mock(SplitLayout.class);
doReturn(dividerBounds).when(out).getDividerBounds();
+ doReturn(dividerBounds).when(out).getRefDividerBounds();
doReturn(leash).when(out).getDividerLeash();
return out;
}
@@ -65,26 +64,24 @@ public class SplitTestUtils {
}
static class TestStageCoordinator extends StageCoordinator {
- final DisplayAreaInfo mDisplayAreaInfo;
+ final ActivityManager.RunningTaskInfo mRootTask;
+ final SurfaceControl mRootLeash;
TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
+ ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
+ DisplayController displayController, DisplayImeController imeController,
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
- SplitscreenEventLogger logger,
- Optional<RecentTasksController> recentTasks,
+ SplitscreenEventLogger logger, Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldController) {
- super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
- sideStage, imeController, insetsController, splitLayout, transitions,
- transactionPool, logger, recentTasks, unfoldController);
+ super(context, displayId, syncQueue, taskOrganizer, mainStage,
+ sideStage, displayController, imeController, insetsController, splitLayout,
+ transitions, transactionPool, logger, recentTasks, unfoldController);
- // Prepare default TaskDisplayArea for testing.
- mDisplayAreaInfo = new DisplayAreaInfo(
- new WindowContainerToken(new IWindowContainerToken.Default()),
- DEFAULT_DISPLAY,
- FEATURE_DEFAULT_TASK_CONTAINER);
- this.onDisplayAreaAppeared(mDisplayAreaInfo);
+ // Prepare root task for testing.
+ mRootTask = new TestRunningTaskInfoBuilder().build();
+ mRootLeash = new SurfaceControl.Builder(new SurfaceSession()).setName("test").build();
+ onTaskAppeared(mRootTask, mRootLeash);
}
}
}
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 1eae625233a0..f847e6eb5e96 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -24,7 +25,11 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitTestUtils.createMockSurface;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -39,7 +44,6 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -60,13 +64,13 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -85,6 +89,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ @Mock private DisplayController mDisplayController;
@Mock private DisplayImeController mDisplayImeController;
@Mock private DisplayInsetsController mDisplayInsetsController;
@Mock private TransactionPool mTransactionPool;
@@ -119,7 +124,7 @@ public class SplitTransitionTests extends ShellTestCase {
mIconProvider, null);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
- mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
+ mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
mTransactionPool, mLogger, Optional.empty(), Optional::empty);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
@@ -133,6 +138,40 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ public void testLaunchToSide() {
+ ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
+ .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
+ ActivityManager.RunningTaskInfo reparentTask = new TestRunningTaskInfoBuilder()
+ .setParentTaskId(mMainStage.mRootTaskInfo.taskId).build();
+
+ // Create a request to start a new task in side stage
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, newTask, null);
+ IBinder transition = mock(IBinder.class);
+ WindowContainerTransaction result =
+ mStageCoordinator.handleRequest(transition, request);
+
+ // it should handle the transition to enter split screen.
+ assertNotNull(result);
+ assertTrue(containsSplitEnter(result));
+
+ // simulate the transition
+ TransitionInfo.Change openChange = createChange(TRANSIT_OPEN, newTask);
+ TransitionInfo.Change reparentChange = createChange(TRANSIT_CHANGE, reparentTask);
+
+ TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+ info.addChange(openChange);
+ info.addChange(reparentChange);
+ mSideStage.onTaskAppeared(newTask, createMockSurface());
+ mMainStage.onTaskAppeared(reparentTask, createMockSurface());
+ boolean accepted = mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(accepted);
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
public void testLaunchPair() {
TransitionInfo info = createEnterPairInfo();
@@ -211,18 +250,20 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
- public void testDismissToHome() {
+ public void testEnterRecents() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
- .setActivityType(ACTIVITY_TYPE_HOME).build();
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .build();
// Create a request to bring home forward
TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
- assertTrue(containsSplitExit(result));
+ assertTrue(result.isEmpty());
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -242,6 +283,64 @@ public class SplitTransitionTests extends ShellTestCase {
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
+ public void testDismissFromBeingOccluded() {
+ enterSplit();
+
+ ActivityManager.RunningTaskInfo normalTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build();
+
+ // Create a request to bring a normal task forward
+ TransitionRequestInfo request =
+ new TransitionRequestInfo(TRANSIT_TO_FRONT, normalTask, null);
+ IBinder transition = mock(IBinder.class);
+ WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+
+ assertTrue(containsSplitExit(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.Change normalChange = createChange(TRANSIT_TO_FRONT, normalTask);
+ TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild);
+ TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild);
+
+ TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+ info.addChange(normalChange);
+ info.addChange(mainChange);
+ info.addChange(sideChange);
+ mMainStage.onTaskVanished(mMainChild);
+ mSideStage.onTaskVanished(mSideChild);
+ mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertFalse(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
+ public void testDismissFromMultiWindowSupport() {
+ enterSplit();
+
+ // simulate the transition
+ TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild);
+ TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild);
+ TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
+ info.addChange(mainChange);
+ info.addChange(sideChange);
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ new WindowContainerTransaction(), mStageCoordinator,
+ EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE);
+ boolean accepted = mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(accepted);
assertFalse(mStageCoordinator.isSplitScreenVisible());
}
@@ -256,8 +355,9 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
info.addChange(mainChange);
info.addChange(sideChange);
- IBinder transition = mStageCoordinator.onSnappedToDismissTransition(
- false /* mainStageToTop */);
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER,
+ STAGE_TYPE_SIDE);
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -320,8 +420,18 @@ public class SplitTransitionTests extends ShellTestCase {
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
- true /* includingTopTask */);
+ mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
+ }
+
+ private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
+ for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+ WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER
+ && op.getContainer() == mStageCoordinator.mRootTaskInfo.token.asBinder()) {
+ return true;
+ }
+ }
+ return false;
}
private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
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 85f6789c3435..061136c65daf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -34,23 +34,24 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.content.res.Configuration;
import android.graphics.Rect;
-import android.window.DisplayAreaInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -79,8 +80,6 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock
private SyncTransactionQueue mSyncQueue;
@Mock
- private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- @Mock
private MainStage mMainStage;
@Mock
private SideStage mSideStage;
@@ -91,6 +90,8 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock
private SplitLayout mSplitLayout;
@Mock
+ private DisplayController mDisplayController;
+ @Mock
private DisplayImeController mDisplayImeController;
@Mock
private DisplayInsetsController mDisplayInsetsController;
@@ -104,16 +105,27 @@ public class StageCoordinatorTests extends ShellTestCase {
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
+ private SurfaceSession mSurfaceSession = new SurfaceSession();
+ private SurfaceControl mRootLeash;
+ private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mStageCoordinator = spy(createStageCoordinator(/* splitLayout */ null));
+ mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mLogger,
+ Optional.empty(), new UnfoldControllerProvider()));
doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+ when(mSplitLayout.isLandscape()).thenReturn(false);
+
+ mRootTask = new TestRunningTaskInfoBuilder().build();
+ mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
+ mStageCoordinator.onTaskAppeared(mRootTask, mRootLeash);
}
@Test
@@ -153,35 +165,42 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testDisplayAreaAppeared_initializesUnfoldControllers() {
- mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class));
-
+ public void testRootTaskAppeared_initializesUnfoldControllers() {
verify(mMainUnfoldController).init();
verify(mSideUnfoldController).init();
+ verify(mStageCoordinator).onRootTaskAppeared();
+ }
+
+ @Test
+ public void testRootTaskInfoChanged_updatesSplitLayout() {
+ mStageCoordinator.onTaskInfoChanged(mRootTask);
+
+ verify(mSplitLayout).updateConfiguration(any(Configuration.class));
}
@Test
public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
- mStageCoordinator = createStageCoordinator(mSplitLayout);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds2);
- verify(mSideUnfoldController).onLayoutChanged(mBounds1);
+ verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
}
@Test
public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
- mStageCoordinator = createStageCoordinator(mSplitLayout);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds1);
- verify(mSideUnfoldController).onLayoutChanged(mBounds2);
+ verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
+ false);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false);
}
@Test
@@ -286,12 +305,11 @@ public class StageCoordinatorTests extends ShellTestCase {
assertEquals(mStageCoordinator.getMainStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT);
}
- private StageCoordinator createStageCoordinator(SplitLayout splitLayout) {
- return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
- mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
- mDisplayImeController, mDisplayInsetsController, splitLayout,
- mTransitions, mTransactionPool, mLogger, Optional.empty(),
- new UnfoldControllerProvider());
+ @Test
+ public void testFinishEnterSplitScreen_applySurfaceLayout() {
+ mStageCoordinator.finishEnterSplitScreen(new SurfaceControl.Transaction());
+
+ verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any());
}
private class UnfoldControllerProvider implements
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 53d5076f5835..157c30bcb6c7 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
@@ -62,7 +62,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests extends ShellTestCase {
private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@Mock
private ShellTaskOrganizer mTaskOrganizer;
@@ -132,6 +132,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
@Test
public void testTaskAppeared_notifiesUnfoldListener() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
@@ -142,6 +143,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
@Test
public void testTaskVanished_notifiesUnfoldListener() {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
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 d92b12e60780..630d0d2c827c 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,6 +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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -297,6 +299,56 @@ public class StartingSurfaceDrawerTests {
assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
}
+ @Test
+ public void testMinimumAnimationDuration() {
+ final long maxDuration = MAX_ANIMATION_DURATION;
+ final long minDuration = MINIMAL_ANIMATION_DURATION;
+
+ final long shortDuration = minDuration - 1;
+ final long medianShortDuration = minDuration + 1;
+ final long medianLongDuration = maxDuration - 1;
+ final long longAppDuration = maxDuration + 1;
+
+ // static icon
+ assertEquals(shortDuration, SplashscreenContentDrawer.getShowingDuration(
+ 0, shortDuration));
+ // median launch + static icon
+ assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration(
+ 0, medianShortDuration));
+ // long launch + static icon
+ assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration(
+ 0, longAppDuration));
+
+ // fast launch + animatable icon
+ assertEquals(shortDuration, SplashscreenContentDrawer.getShowingDuration(
+ shortDuration, shortDuration));
+ assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration(
+ medianShortDuration, shortDuration));
+ assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration(
+ longAppDuration, shortDuration));
+
+ // median launch + animatable icon
+ assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration(
+ shortDuration, medianShortDuration));
+ assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration(
+ medianShortDuration, medianShortDuration));
+ assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration(
+ longAppDuration, medianShortDuration));
+ // between min < max launch + animatable icon
+ assertEquals(medianLongDuration, SplashscreenContentDrawer.getShowingDuration(
+ medianShortDuration, medianLongDuration));
+ assertEquals(maxDuration, SplashscreenContentDrawer.getShowingDuration(
+ medianLongDuration, medianShortDuration));
+
+ // long launch + animatable icon
+ assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration(
+ shortDuration, longAppDuration));
+ assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration(
+ medianShortDuration, longAppDuration));
+ assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration(
+ longAppDuration, longAppDuration));
+ }
+
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
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 e39171343bb9..a0b12976b467 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
@@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -54,7 +55,9 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
@@ -84,8 +87,6 @@ import com.android.wm.shell.common.TransactionPool;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@@ -93,7 +94,7 @@ import java.util.ArrayList;
* Tests for the shell transitions.
*
* Build/Install/Run:
- * atest WMShellUnitTests:ShellTransitionTests
+ * atest WMShellUnitTests:ShellTransitionTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -106,6 +107,7 @@ public class ShellTransitionTests {
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@Before
public void setUp() {
@@ -590,6 +592,90 @@ public class ShellTransitionTests {
.setRotate().build())
.build();
assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+
+ // Seamless if display is explicitly seamless.
+ final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
+ }
+
+ @Test
+ public void testRunWhenIdle() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ Runnable runnable1 = mock(Runnable.class);
+ Runnable runnable2 = mock(Runnable.class);
+ Runnable runnable3 = mock(Runnable.class);
+ Runnable runnable4 = mock(Runnable.class);
+
+ transitions.runOnIdle(runnable1);
+
+ // runnable1 is executed immediately because there are no active transitions.
+ verify(runnable1, times(1)).run();
+
+ clearInvocations(runnable1);
+
+ IBinder transitToken1 = new Binder();
+ transitions.requestStartTransition(transitToken1,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ assertEquals(1, mDefaultHandler.activeCount());
+
+ transitions.runOnIdle(runnable2);
+ transitions.runOnIdle(runnable3);
+
+ // runnable2 and runnable3 aren't executed immediately because there is an active
+ // transaction.
+
+ IBinder transitToken2 = new Binder();
+ transitions.requestStartTransition(transitToken2,
+ new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+ TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ assertEquals(1, mDefaultHandler.activeCount());
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ // first transition finished
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+ // But now the "queued" transition is running
+ assertEquals(1, mDefaultHandler.activeCount());
+
+ // runnable2 and runnable3 are still not executed because the second transition is still
+ // active.
+ verify(runnable2, times(0)).run();
+ verify(runnable3, times(0)).run();
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+
+ // runnable2 and runnable3 are executed after the second transition finishes because there
+ // are no other active transitions, runnable1 isn't executed again.
+ verify(runnable1, times(0)).run();
+ verify(runnable2, times(1)).run();
+ verify(runnable3, times(1)).run();
+
+ clearInvocations(runnable2);
+ clearInvocations(runnable3);
+
+ transitions.runOnIdle(runnable4);
+
+ // runnable4 is executed immediately because there are no active transitions, all other
+ // runnables aren't executed again.
+ verify(runnable1, times(0)).run();
+ verify(runnable2, times(0)).run();
+ verify(runnable3, times(0)).run();
+ verify(runnable4, times(1)).run();
}
class TransitionInfoBuilder {
@@ -741,7 +827,7 @@ public class ShellTransitionTests {
IWindowManager mockWM = mock(IWindowManager.class);
final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
try {
- doReturn(new int[] {DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
+ doReturn(new int[]{DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
} catch (RemoteException e) {
// No remote stuff happening, so this can't be hit
}
@@ -752,7 +838,7 @@ public class ShellTransitionTests {
private Transitions createTestTransitions() {
return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
- mContext, mMainExecutor, mAnimExecutor);
+ mContext, mMainExecutor, mMainHandler, mAnimExecutor);
}
//
// private class TestDisplayController extends DisplayController {
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 0cde3d1242c8..136fc6ca4e2a 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
#include "utils/ByteOrder.h"
@@ -600,6 +601,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
return base::unexpected(result.error());
}
+ bool overlaid = false;
if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) {
for (const auto& id_map : package_group.overlays_) {
auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
@@ -616,6 +618,27 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie});
+ if (auto path = apk_assets_[result->cookie]->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
}
continue;
}
@@ -646,6 +669,9 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
overlay_result->cookie});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
+ overlaid = true;
}
}
}
@@ -654,6 +680,10 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
last_resolution_.cookie = result->cookie;
last_resolution_.type_string_ref = result->type_string_ref;
last_resolution_.entry_string_ref = result->entry_string_ref;
+ last_resolution_.best_config_name = result->config.toString();
+ if (!overlaid) {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
}
return result;
@@ -671,8 +701,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
uint32_t best_offset = 0U;
uint32_t type_flags = 0U;
- std::vector<Resolution::Step> resolution_steps;
-
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
@@ -725,7 +753,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
resolution_type = Resolution::Step::Type::OVERLAID;
} else {
if (UNLIKELY(logging_enabled)) {
- resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
+ last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
this_config.toString(),
cookie});
}
@@ -742,7 +770,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
if (!offset.has_value()) {
if (UNLIKELY(logging_enabled)) {
- resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+ last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
this_config.toString(),
cookie});
}
@@ -806,6 +834,8 @@ void AssetManager2::ResetResourceResolution() const {
last_resolution_.steps.clear();
last_resolution_.type_string_ref = StringPoolRef();
last_resolution_.entry_string_ref = StringPoolRef();
+ last_resolution_.best_config_name.clear();
+ last_resolution_.best_package_name.clear();
}
void AssetManager2::SetResourceResolutionLoggingEnabled(bool enabled) {
@@ -865,9 +895,34 @@ std::string AssetManager2::GetLastResourceResolution() const {
}
}
+ log_stream << "\nBest matching is from "
+ << (last_resolution_.best_config_name.isEmpty() ? "default"
+ : last_resolution_.best_config_name)
+ << " configuration of " << last_resolution_.best_package_name;
return log_stream.str();
}
+base::expected<uint32_t, NullOrIOError> AssetManager2::GetParentThemeResourceId(uint32_t resid)
+const {
+ auto entry = FindEntry(resid, 0u /* density_override */,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (!entry.has_value()) {
+ return base::unexpected(entry.error());
+ }
+
+ auto entry_map = std::get_if<incfs::verified_map_ptr<ResTable_map_entry>>(&entry->entry);
+ if (entry_map == nullptr) {
+ // Not a bag, nothing to do.
+ return base::unexpected(std::nullopt);
+ }
+
+ auto map = *entry_map;
+ const uint32_t parent_resid = dtohl(map->parent.ident);
+
+ return parent_resid;
+}
+
base::expected<AssetManager2::ResourceName, NullOrIOError> AssetManager2::GetResourceName(
uint32_t resid) const {
auto result = FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */,
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 13aff3812767..35b6170fae5b 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -650,22 +650,25 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
// Retrieve all the resource ids belonging to this policy chunk
- std::unordered_set<uint32_t> ids;
const auto ids_begin = overlayable_child_chunk.data_ptr().convert<ResTable_ref>();
const auto ids_end = ids_begin + dtohl(policy_header->entry_count);
+ std::unordered_set<uint32_t> ids;
+ ids.reserve(ids_end - ids_begin);
for (auto id_iter = ids_begin; id_iter != ids_end; ++id_iter) {
if (!id_iter) {
+ LOG(ERROR) << "NULL ResTable_ref record??";
return {};
}
ids.insert(dtohl(id_iter->ident));
}
// Add the pairing of overlayable properties and resource ids to the package
- OverlayableInfo overlayable_info{};
- overlayable_info.name = name;
- overlayable_info.actor = actor;
- overlayable_info.policy_flags = policy_header->policy_flags;
- loaded_package->overlayable_infos_.emplace_back(overlayable_info, ids);
+ OverlayableInfo overlayable_info {
+ .name = name,
+ .actor = actor,
+ .policy_flags = policy_header->policy_flags
+ };
+ loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
loaded_package->defines_overlayable_ = true;
break;
}
@@ -692,7 +695,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
break;
}
- std::unordered_set<uint32_t> finalized_ids;
const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>();
if (!lib_alias) {
LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small.";
@@ -705,8 +707,11 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>();
const auto entry_end = entry_begin + dtohl(lib_alias->count);
+ std::unordered_set<uint32_t> finalized_ids;
+ finalized_ids.reserve(entry_end - entry_begin);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
if (!entry_iter) {
+ LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
return {};
}
auto finalized_id = dtohl(entry_iter->finalizedResId);
@@ -717,8 +722,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
auto staged_id = dtohl(entry_iter->stagedResId);
- auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id,
- finalized_id));
+ auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
if (!success) {
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
staged_id);
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index 3eedda88fdce..d87a3ce72177 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -29,40 +29,33 @@ using ::android::StringPiece;
namespace android {
-void LocaleValue::set_language(const char* language_chars) {
+template <size_t N, class Transformer>
+static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) {
size_t i = 0;
- while ((*language_chars) != '\0') {
- language[i++] = ::tolower(*language_chars);
- language_chars++;
+ while (i < N && (*source) != '\0') {
+ dest[i++] = t(i, *source);
+ source++;
+ }
+ while (i < N) {
+ dest[i++] = '\0';
}
}
+void LocaleValue::set_language(const char* language_chars) {
+ safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); });
+}
+
void LocaleValue::set_region(const char* region_chars) {
- size_t i = 0;
- while ((*region_chars) != '\0') {
- region[i++] = ::toupper(*region_chars);
- region_chars++;
- }
+ safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); });
}
void LocaleValue::set_script(const char* script_chars) {
- size_t i = 0;
- while ((*script_chars) != '\0') {
- if (i == 0) {
- script[i++] = ::toupper(*script_chars);
- } else {
- script[i++] = ::tolower(*script_chars);
- }
- script_chars++;
- }
+ safe_transform_copy(script_chars, script,
+ [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); });
}
void LocaleValue::set_variant(const char* variant_chars) {
- size_t i = 0;
- while ((*variant_chars) != '\0') {
- variant[i++] = *variant_chars;
- variant_chars++;
- }
+ safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; });
}
static inline bool is_alpha(const std::string& str) {
@@ -234,6 +227,10 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
return static_cast<ssize_t>(iter - start_iter);
}
+// Make sure the following memcpy's are properly sized.
+static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script));
+static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant));
+
void LocaleValue::InitFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index cae2d0bc16b3..5e8a623d4205 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2677,30 +2677,27 @@ bool ResTable_config::isBetterThan(const ResTable_config& o,
// DENSITY_ANY is now dealt with. We should look to
// pick a density bucket and potentially scale it.
// Any density is potentially useful
- // because the system will scale it. Scaling down
- // is generally better than scaling up.
+ // because the system will scale it. Always prefer
+ // scaling down.
int h = thisDensity;
int l = otherDensity;
bool bImBigger = true;
if (l > h) {
- int t = h;
- h = l;
- l = t;
+ std::swap(l, h);
bImBigger = false;
}
- if (requestedDensity >= h) {
- // requested value higher than both l and h, give h
+ if (h == requestedDensity) {
+ // This handles the case where l == h == requestedDensity.
+ // In that case, this and o are equally good so both
+ // true and false are valid. This preserves previous
+ // behavior.
return bImBigger;
- }
- if (l >= requestedDensity) {
+ } else if (l >= requestedDensity) {
// requested value lower than both l and h, give l
return !bImBigger;
- }
- // saying that scaling down is 2x better than up
- if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) {
- return !bImBigger;
} else {
+ // otherwise give h
return bImBigger;
}
}
diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
index 9ebc9969a730..8abe79d01642 100644
--- a/libs/androidfw/TEST_MAPPING
+++ b/libs/androidfw/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsResourcesLoaderTests"
+ },
+ {
+ "name": "libandroidfw_tests"
}
]
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 7d01395bbbbc..1bde792da2ba 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -192,6 +192,12 @@ class AssetManager2 {
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
Asset::AccessMode mode) const;
+ // Returns the resource id of parent style of the specified theme.
+ //
+ // Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data
+ // failed.
+ base::expected<uint32_t, NullOrIOError> GetParentThemeResourceId(uint32_t resid) const;
+
// Returns the resource name of the specified resource ID.
//
// Utf8 strings are preferred, and only if they are unavailable are the Utf16 variants populated.
@@ -486,6 +492,12 @@ class AssetManager2 {
// Steps taken to resolve last resource.
std::vector<Step> steps;
+
+ // The configuration name of the best resource found.
+ String8 best_config_name;
+
+ // The package name of the best resource found.
+ String8 best_package_name;
};
// Record of the last resolved resource's resolution path.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3c4ee4e63a76..4394740e44ba 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -766,7 +766,9 @@ TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) {
auto result = assetmanager.GetLastResourceResolution();
EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
"\tFor config - de\n"
- "\tFound initial: basic/basic.apk", result);
+ "\tFound initial: basic/basic.apk\n"
+ "Best matching is from default configuration of com.android.basic",
+ result);
}
TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
@@ -787,7 +789,9 @@ TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
"\tFor config - de\n"
"\tFound initial: basic/basic.apk\n"
- "\tFound better: basic/basic_de_fr.apk - de", result);
+ "\tFound better: basic/basic_de_fr.apk - de\n"
+ "Best matching is from de configuration of com.android.basic",
+ result);
}
TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index b54915f03c29..698c36f09301 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -75,6 +75,9 @@ TEST(ConfigTest, shouldSelectBestDensity) {
configs.add(buildDensityConfig(int(ResTable_config::DENSITY_HIGH) + 20));
ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+ configs.add(buildDensityConfig(int(ResTable_config::DENSITY_XHIGH) - 1));
+ ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
expectedBest = buildDensityConfig(ResTable_config::DENSITY_XHIGH);
configs.add(expectedBest);
ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp
index c7b3eba1451f..8c49350796ec 100644
--- a/libs/androidfw/tests/PosixUtils_test.cpp
+++ b/libs/androidfw/tests/PosixUtils_test.cpp
@@ -30,14 +30,14 @@ TEST(PosixUtilsTest, AbsolutePathToBinary) {
const auto result = ExecuteBinary({"/bin/date", "--help"});
ASSERT_THAT(result, NotNull());
ASSERT_EQ(result->status, 0);
- ASSERT_EQ(result->stdout_str.find("usage: date "), 0);
+ ASSERT_GE(result->stdout_str.find("usage: date "), 0);
}
TEST(PosixUtilsTest, RelativePathToBinary) {
const auto result = ExecuteBinary({"date", "--help"});
ASSERT_THAT(result, NotNull());
ASSERT_EQ(result->status, 0);
- ASSERT_EQ(result->stdout_str.find("usage: date "), 0);
+ ASSERT_GE(result->stdout_str.find("usage: date "), 0);
}
TEST(PosixUtilsTest, BadParameters) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 504082db41bb..ece150a4bd45 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -106,6 +106,8 @@ cc_defaults {
target: {
android: {
shared_libs: [
+ "android.hardware.graphics.common-V3-ndk",
+ "android.hardware.graphics.common@1.2",
"liblog",
"libcutils",
"libutils",
@@ -125,9 +127,11 @@ cc_defaults {
static_libs: [
"libEGL_blobCache",
"libprotoutil",
+ "libshaders",
"libstatslog_hwui",
"libstatspull_lazy",
"libstatssocket_lazy",
+ "libtonemap",
],
},
host: {
@@ -255,7 +259,6 @@ cc_defaults {
"apex/android_bitmap.cpp",
"apex/android_canvas.cpp",
"apex/jni_runtime.cpp",
- "apex/renderthread.cpp",
],
},
host: {
@@ -342,6 +345,7 @@ cc_defaults {
"jni/PathEffect.cpp",
"jni/PathMeasure.cpp",
"jni/Picture.cpp",
+ "jni/Region.cpp",
"jni/Shader.cpp",
"jni/RenderEffect.cpp",
"jni/Typeface.cpp",
@@ -391,7 +395,6 @@ cc_defaults {
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
"jni/MovieImpl.cpp",
- "jni/Region.cpp", // requires libbinder_ndk
"jni/pdf/PdfDocument.cpp",
"jni/pdf/PdfEditor.cpp",
"jni/pdf/PdfRenderer.cpp",
@@ -532,7 +535,10 @@ cc_defaults {
target: {
android: {
- header_libs: ["libandroid_headers_private"],
+ header_libs: [
+ "libandroid_headers_private",
+ "libtonemap_headers",
+ ],
srcs: [
"hwui/AnimatedImageThread.cpp",
@@ -571,6 +577,7 @@ cc_defaults {
"HardwareBitmapUploader.cpp",
"HWUIProperties.sysprop",
"JankTracker.cpp",
+ "FrameMetricsReporter.cpp",
"Layer.cpp",
"LayerUpdateQueue.cpp",
"ProfileData.cpp",
@@ -676,6 +683,7 @@ cc_test {
"tests/unit/FatVectorTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
"tests/unit/JankTrackerTests.cpp",
+ "tests/unit/FrameMetricsReporterTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
"tests/unit/LinearAllocatorTests.cpp",
"tests/unit/MatrixTests.cpp",
diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h
index 6d387f9ef43d..3df5c3c9caed 100644
--- a/libs/hwui/ColorMode.h
+++ b/libs/hwui/ColorMode.h
@@ -29,6 +29,8 @@ enum class ColorMode {
Hdr = 2,
// HDR Rec2020 + 1010102
Hdr10 = 3,
+ // Alpha 8
+ A8 = 4,
};
} // namespace android::uirenderer
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 8d112d1c64bf..a5c0924579eb 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -20,9 +20,11 @@
// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
#include <surfacetexture/surface_texture_platform.h>
+
#include "AutoBackendTextureRelease.h"
#include "Matrix.h"
#include "Properties.h"
+#include "android/hdr_metadata.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
#include "renderthread/RenderThread.h"
@@ -147,14 +149,20 @@ void DeferredLayerUpdater::apply() {
mUpdateTexImage = false;
float transformMatrix[16];
android_dataspace dataspace;
+ AHdrMetadataType hdrMetadataType;
+ android_cta861_3_metadata cta861_3;
+ android_smpte2086_metadata smpte2086;
int slot;
bool newContent = false;
+ ARect currentCrop;
+ uint32_t outTransform;
// Note: ASurfaceTexture_dequeueBuffer discards all but the last frame. This
// is necessary if the SurfaceTexture queue is in synchronous mode, and we
// cannot tell which mode it is in.
AHardwareBuffer* hardwareBuffer = ASurfaceTexture_dequeueBuffer(
- mSurfaceTexture.get(), &slot, &dataspace, transformMatrix, &newContent,
- createReleaseFence, fenceWait, this);
+ mSurfaceTexture.get(), &slot, &dataspace, &hdrMetadataType, &cta861_3,
+ &smpte2086, transformMatrix, &outTransform, &newContent, createReleaseFence,
+ fenceWait, this, &currentCrop);
if (hardwareBuffer) {
mCurrentSlot = slot;
@@ -165,12 +173,24 @@ void DeferredLayerUpdater::apply() {
// (invoked by createIfNeeded) will add a ref to the AHardwareBuffer.
AHardwareBuffer_release(hardwareBuffer);
if (layerImage.get()) {
- SkMatrix textureTransform;
- mat4(transformMatrix).copyTo(textureTransform);
// force filtration if buffer size != layer size
bool forceFilter =
mWidth != layerImage->width() || mHeight != layerImage->height();
- updateLayer(forceFilter, textureTransform, layerImage);
+ SkRect currentCropRect =
+ SkRect::MakeLTRB(currentCrop.left, currentCrop.top, currentCrop.right,
+ currentCrop.bottom);
+
+ float maxLuminanceNits = -1.f;
+ if (hdrMetadataType & HDR10_SMPTE2086) {
+ maxLuminanceNits = std::max(smpte2086.maxLuminance, maxLuminanceNits);
+ }
+
+ if (hdrMetadataType & HDR10_CTA861_3) {
+ maxLuminanceNits =
+ std::max(cta861_3.maxContentLightLevel, maxLuminanceNits);
+ }
+ updateLayer(forceFilter, layerImage, outTransform, currentCropRect,
+ maxLuminanceNits);
}
}
}
@@ -182,13 +202,16 @@ void DeferredLayerUpdater::apply() {
}
}
-void DeferredLayerUpdater::updateLayer(bool forceFilter, const SkMatrix& textureTransform,
- const sk_sp<SkImage>& layerImage) {
+void DeferredLayerUpdater::updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage,
+ const uint32_t transform, SkRect currentCrop,
+ float maxLuminanceNits) {
mLayer->setBlend(mBlend);
mLayer->setForceFilter(forceFilter);
mLayer->setSize(mWidth, mHeight);
- mLayer->getTexTransform() = textureTransform;
+ mLayer->setCurrentCropRect(currentCrop);
+ mLayer->setWindowTransform(transform);
mLayer->setImage(layerImage);
+ mLayer->setMaxLuminanceNits(maxLuminanceNits);
}
void DeferredLayerUpdater::detachSurfaceTexture() {
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 8f79c4ec97b8..9a4c5505fa35 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -90,8 +90,8 @@ public:
void detachSurfaceTexture();
- void updateLayer(bool forceFilter, const SkMatrix& textureTransform,
- const sk_sp<SkImage>& layerImage);
+ void updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage, const uint32_t transform,
+ SkRect currentCrop, float maxLuminanceNits = -1.f);
void destroyLayer();
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index fb3e21fc1571..4ec782f6fec0 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -27,6 +27,7 @@ X(ClipPath)
X(ClipRect)
X(ClipRRect)
X(ClipRegion)
+X(ResetClip)
X(DrawPaint)
X(DrawBehind)
X(DrawPath)
diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h
index ef1f5aabcbd8..3ea49518eecd 100644
--- a/libs/hwui/FrameMetricsObserver.h
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -26,6 +26,13 @@ public:
virtual void notify(const int64_t* buffer) = 0;
bool waitForPresentTime() const { return mWaitForPresentTime; };
+ void reportMetricsFrom(uint64_t frameNumber, int32_t surfaceControlId) {
+ mAttachedFrameNumber = frameNumber;
+ mSurfaceControlId = surfaceControlId;
+ };
+ uint64_t attachedFrameNumber() const { return mAttachedFrameNumber; };
+ int32_t attachedSurfaceControlId() const { return mSurfaceControlId; };
+
/**
* Create a new metrics observer. An observer that watches present time gets notified at a
* different time than the observer that doesn't.
@@ -38,10 +45,29 @@ public:
* WARNING! This observer may not receive metrics for the last several frames that the app
* produces.
*/
- FrameMetricsObserver(bool waitForPresentTime) : mWaitForPresentTime(waitForPresentTime) {}
+ FrameMetricsObserver(bool waitForPresentTime)
+ : mWaitForPresentTime(waitForPresentTime)
+ , mSurfaceControlId(INT32_MAX)
+ , mAttachedFrameNumber(UINT64_MAX) {}
private:
const bool mWaitForPresentTime;
+
+ // The id of the surface control (mSurfaceControlGenerationId in CanvasContext)
+ // for which the mAttachedFrameNumber applies to. We rely on this value being
+ // an increasing counter. We will report metrics:
+ // - for all frames if the frame comes from a surface with a surfaceControlId
+ // that is strictly greater than mSurfaceControlId.
+ // - for all frames with a frame number greater than or equal to mAttachedFrameNumber
+ // if the frame comes from a surface with a surfaceControlId that is equal to the
+ // mSurfaceControlId.
+ // We will never report metrics if the frame comes from a surface with a surfaceControlId
+ // that is strictly smaller than mSurfaceControlId.
+ int32_t mSurfaceControlId;
+
+ // The frame number the metrics observer was attached on. Metrics will be sent from this frame
+ // number (inclusive) onwards in the case that the surface id is equal to mSurfaceControlId.
+ uint64_t mAttachedFrameNumber;
};
} // namespace uirenderer
diff --git a/libs/hwui/FrameMetricsReporter.cpp b/libs/hwui/FrameMetricsReporter.cpp
new file mode 100644
index 000000000000..ee32ea17bfaf
--- /dev/null
+++ b/libs/hwui/FrameMetricsReporter.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FrameMetricsReporter.h"
+
+namespace android {
+namespace uirenderer {
+
+void FrameMetricsReporter::reportFrameMetrics(const int64_t* stats, bool hasPresentTime,
+ uint64_t frameNumber, int32_t surfaceControlId) {
+ FatVector<sp<FrameMetricsObserver>, 10> copy;
+ {
+ std::lock_guard lock(mObserversLock);
+ copy.reserve(mObservers.size());
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ auto observer = mObservers[i];
+
+ if (CC_UNLIKELY(surfaceControlId < observer->attachedSurfaceControlId())) {
+ // Don't notify if the metrics are from a frame that was run on an old
+ // surface (one from before the observer was attached).
+ ALOGV("skipped reporting metrics from old surface %d", surfaceControlId);
+ continue;
+ } else if (CC_UNLIKELY(surfaceControlId == observer->attachedSurfaceControlId() &&
+ frameNumber < observer->attachedFrameNumber())) {
+ // Don't notify if the metrics are from a frame that was queued by the
+ // BufferQueueProducer on the render thread before the observer was attached.
+ ALOGV("skipped reporting metrics from old frame %ld", (long)frameNumber);
+ continue;
+ }
+
+ const bool wantsPresentTime = observer->waitForPresentTime();
+ if (hasPresentTime == wantsPresentTime) {
+ copy.push_back(observer);
+ }
+ }
+ }
+ for (size_t i = 0; i < copy.size(); i++) {
+ copy[i]->notify(stats);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
index 0ac025fb01db..7e51df7ce6fc 100644
--- a/libs/hwui/FrameMetricsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -63,23 +63,14 @@ public:
* If an observer does not want present time, only notify when 'hasPresentTime' is false.
* Never notify both types of observers from the same callback, because the callback with
* 'hasPresentTime' is sent at a different time than the one without.
+ *
+ * The 'frameNumber' and 'surfaceControlId' associated to the frame whose's stats are being
+ * reported are used to determine whether or not the stats should be reported. We won't report
+ * stats of frames that are from "old" surfaces (i.e. with surfaceControlIds older than the one
+ * the observer was attached on) nor those that are from "old" frame numbers.
*/
- void reportFrameMetrics(const int64_t* stats, bool hasPresentTime) {
- FatVector<sp<FrameMetricsObserver>, 10> copy;
- {
- std::lock_guard lock(mObserversLock);
- copy.reserve(mObservers.size());
- for (size_t i = 0; i < mObservers.size(); i++) {
- const bool wantsPresentTime = mObservers[i]->waitForPresentTime();
- if (hasPresentTime == wantsPresentTime) {
- copy.push_back(mObservers[i]);
- }
- }
- }
- for (size_t i = 0; i < copy.size(); i++) {
- copy[i]->notify(stats);
- }
- }
+ void reportFrameMetrics(const int64_t* stats, bool hasPresentTime, uint64_t frameNumber,
+ int32_t surfaceControlId);
private:
FatVector<sp<FrameMetricsObserver>, 10> mObservers GUARDED_BY(mObserversLock);
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index db3a1081e32c..c24cabb287de 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -287,29 +287,34 @@ private:
std::mutex mVkLock;
};
-bool HardwareBitmapUploader::hasFP16Support() {
- static std::once_flag sOnce;
- static bool hasFP16Support = false;
-
- // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so
- // we don't need to double-check the GLES version/extension.
- std::call_once(sOnce, []() {
- AHardwareBuffer_Desc desc = {
- .width = 1,
- .height = 1,
- .layers = 1,
- .format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
- AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
- AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
- };
- UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
- hasFP16Support = buffer != nullptr;
- });
+static bool checkSupport(AHardwareBuffer_Format format) {
+ AHardwareBuffer_Desc desc = {
+ .width = 1,
+ .height = 1,
+ .layers = 1,
+ .format = format,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+ };
+ UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
+ return buffer != nullptr;
+}
+bool HardwareBitmapUploader::hasFP16Support() {
+ static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT);
return hasFP16Support;
}
+bool HardwareBitmapUploader::has1010102Support() {
+ static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM);
+ return has101012Support;
+}
+
+bool HardwareBitmapUploader::hasAlpha8Support() {
+ static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM);
+ return hasAlpha8Support;
+}
+
static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
FormatInfo formatInfo;
switch (skBitmap.info().colorType()) {
@@ -350,6 +355,26 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
formatInfo.type = GL_UNSIGNED_BYTE;
formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
+ case kRGBA_1010102_SkColorType:
+ formatInfo.isSupported = HardwareBitmapUploader::has1010102Support();
+ if (formatInfo.isSupported) {
+ formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ } else {
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+ }
+ formatInfo.format = GL_RGBA;
+ break;
+ case kAlpha_8_SkColorType:
+ formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
+ formatInfo.format = GL_R8;
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
+ break;
default:
ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
formatInfo.valid = false;
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index ad7a95a4fa03..81057a24c29c 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -29,10 +29,14 @@ public:
#ifdef __ANDROID__
static bool hasFP16Support();
+ static bool has1010102Support();
+ static bool hasAlpha8Support();
#else
static bool hasFP16Support() {
return true;
}
+ static bool has1010102Support() { return true; }
+ static bool hasAlpha8Support() { return true; }
#endif
};
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 34e5577066f9..1e5be6c3eed7 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -111,19 +111,22 @@ void JankTracker::calculateLegacyJank(FrameInfo& frame) REQUIRES(mDataMutex) {
// the actual time spent blocked.
nsecs_t forgiveAmount =
std::min(expectedDequeueDuration, frame[FrameInfoIndex::DequeueBufferDuration]);
- LOG_ALWAYS_FATAL_IF(forgiveAmount >= totalDuration,
- "Impossible dequeue duration! dequeue duration reported %" PRId64
- ", total duration %" PRId64,
- forgiveAmount, totalDuration);
+ if (forgiveAmount >= totalDuration) {
+ ALOGV("Impossible dequeue duration! dequeue duration reported %" PRId64
+ ", total duration %" PRId64,
+ forgiveAmount, totalDuration);
+ return;
+ }
totalDuration -= forgiveAmount;
}
}
- LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64 " start=%" PRIi64
- " gpuComplete=%" PRIi64, totalDuration,
- frame[FrameInfoIndex::IntendedVsync],
- frame[FrameInfoIndex::GpuCompleted]);
-
+ if (totalDuration <= 0) {
+ ALOGV("Impossible totalDuration %" PRId64 " start=%" PRIi64 " gpuComplete=%" PRIi64,
+ totalDuration, frame[FrameInfoIndex::IntendedVsync],
+ frame[FrameInfoIndex::GpuCompleted]);
+ return;
+ }
// Only things like Surface.lockHardwareCanvas() are exempt from tracking
if (CC_UNLIKELY(frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS)) {
@@ -164,7 +167,8 @@ void JankTracker::calculateLegacyJank(FrameInfo& frame) REQUIRES(mDataMutex) {
- lastFrameOffset + mFrameIntervalLegacy;
}
-void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsReporter>& reporter) {
+void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsReporter>& reporter,
+ int64_t frameNumber, int32_t surfaceControlId) {
std::lock_guard lock(mDataMutex);
calculateLegacyJank(frame);
@@ -173,7 +177,10 @@ void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsRepo
int64_t totalDuration = frame.duration(FrameInfoIndex::IntendedVsync,
FrameInfoIndex::FrameCompleted);
- LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
+ if (totalDuration <= 0) {
+ ALOGV("Impossible totalDuration %" PRId64, totalDuration);
+ return;
+ }
mData->reportFrame(totalDuration);
(*mGlobalData)->reportFrame(totalDuration);
@@ -253,7 +260,8 @@ void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsRepo
}
if (CC_UNLIKELY(reporter.get() != nullptr)) {
- reporter->reportFrameMetrics(frame.data(), false /* hasPresentTime */);
+ reporter->reportFrameMetrics(frame.data(), false /* hasPresentTime */, frameNumber,
+ surfaceControlId);
}
}
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index bdb784dc8747..bcd031efa78d 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -57,7 +57,8 @@ public:
}
FrameInfo* startFrame() { return &mFrames.next(); }
- void finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsReporter>& reporter);
+ void finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsReporter>& reporter,
+ int64_t frameNumber, int32_t surfaceId);
// Calculates the 'legacy' jank information, i.e. with outdated refresh rate information and
// without GPU completion or deadlined information.
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index b14ade97ca5f..9053c1240957 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -20,6 +20,8 @@
#include "utils/Color.h"
#include "utils/MathUtils.h"
+#include <log/log.h>
+
namespace android {
namespace uirenderer {
@@ -33,7 +35,6 @@ Layer::Layer(RenderState& renderState, sk_sp<SkColorFilter> colorFilter, int alp
// preserves the old inc/dec ref locations. This should be changed...
incStrong(nullptr);
renderState.registerLayer(this);
- texTransform.setIdentity();
transform.setIdentity();
}
@@ -90,7 +91,7 @@ static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, cons
void Layer::draw(SkCanvas* canvas) {
GrRecordingContext* context = canvas->recordingContext();
if (context == nullptr) {
- SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+ ALOGD("Attempting to draw LayerDrawable into an unsupported surface");
return;
}
SkMatrix layerTransform = getTransform();
@@ -99,7 +100,6 @@ void Layer::draw(SkCanvas* canvas) {
const int layerHeight = getHeight();
if (layerImage) {
SkMatrix textureMatrixInv;
- textureMatrixInv = getTexTransform();
// TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
// use bottom left origin and remove flipV and invert transformations.
SkMatrix flipV;
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index e99e76299317..47eb5d3bfb83 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -74,10 +74,18 @@ public:
void setColorFilter(sk_sp<SkColorFilter> filter) { mColorFilter = filter; };
- inline SkMatrix& getTexTransform() { return texTransform; }
-
inline SkMatrix& getTransform() { return transform; }
+ inline SkRect getCurrentCropRect() { return mCurrentCropRect; }
+
+ inline void setCurrentCropRect(const SkRect currentCropRect) {
+ mCurrentCropRect = currentCropRect;
+ }
+
+ inline void setWindowTransform(uint32_t windowTransform) { mWindowTransform = windowTransform; }
+
+ inline uint32_t getWindowTransform() { return mWindowTransform; }
+
/**
* Posts a decStrong call to the appropriate thread.
* Thread-safe.
@@ -88,6 +96,12 @@ public:
inline sk_sp<SkImage> getImage() const { return this->layerImage; }
+ inline void setMaxLuminanceNits(float maxLuminanceNits) {
+ mMaxLuminanceNits = maxLuminanceNits;
+ }
+
+ inline float getMaxLuminanceNits() { return mMaxLuminanceNits; }
+
void draw(SkCanvas* canvas);
protected:
@@ -116,14 +130,19 @@ private:
SkBlendMode mode;
/**
- * Optional texture coordinates transform.
+ * Optional transform.
*/
- SkMatrix texTransform;
+ SkMatrix transform;
/**
- * Optional transform.
+ * Optional crop
*/
- SkMatrix transform;
+ SkRect mCurrentCropRect;
+
+ /**
+ * Optional transform
+ */
+ uint32_t mWindowTransform;
/**
* An image backing the layer.
@@ -145,6 +164,11 @@ private:
*/
bool mBlend = false;
+ /**
+ * Max input luminance if the layer is HDR
+ */
+ float mMaxLuminanceNits = -1;
+
}; // struct Layer
} // namespace uirenderer
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 2eb2c7c7e299..e16fd8c38c75 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -88,14 +88,10 @@ public:
bool getShouldClip() const { return mShouldClip; }
- bool willClip() const {
- // only round rect outlines can be used for clipping
- return mShouldClip && (mType == Type::RoundRect);
- }
+ bool willClip() const { return mShouldClip; }
- bool willRoundRectClip() const {
- // only round rect outlines can be used for clipping
- return willClip() && MathUtils::isPositive(mRadius);
+ bool willComplexClip() const {
+ return mShouldClip && (mType != Type::RoundRect || MathUtils::isPositive(mRadius));
}
bool getAsRoundRect(Rect* outRect, float* outRadius) const {
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index dd8439647fd3..3d0ca0a10851 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -137,6 +137,7 @@ void ProfileData::dump(int fd) const {
histogramGPUForEach([fd](HistogramEntry entry) {
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
});
+ dprintf(fd, "\n");
}
uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index bcfe9c3ecab4..86ae3995eeed 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -69,7 +69,6 @@ RenderPipelineType Properties::sRenderPipelineType = RenderPipelineType::NotInit
bool Properties::enableHighContrastText = false;
bool Properties::waitForGpuCompletion = false;
-bool Properties::forceDrawFrame = false;
bool Properties::filterOutTestOverhead = false;
bool Properties::disableVsync = false;
@@ -135,7 +134,7 @@ bool Properties::load() {
skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
SkAndroidFrameworkTraceUtil::setEnableTracing(
- base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
+ base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, true));
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index a743d30939d0..4cce87ad1a2f 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -243,18 +243,14 @@ CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& sr
static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
sk_sp<SkImage> image =
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
- return copyImageInto(image, texTransform, srcRect, bitmap);
+ return copyImageInto(image, srcRect, bitmap);
}
CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
LOG_ALWAYS_FATAL_IF(!hwBitmap->isHardware());
Rect srcRect;
- Matrix4 transform;
- transform.loadScale(1, -1, 1);
- transform.translate(0, -1);
-
- return copyImageInto(hwBitmap->makeImage(), transform, srcRect, bitmap);
+ return copyImageInto(hwBitmap->makeImage(), srcRect, bitmap);
}
CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) {
@@ -279,14 +275,11 @@ CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap
CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
Rect srcRect;
- Matrix4 transform;
- transform.loadScale(1, -1, 1);
- transform.translate(0, -1);
- return copyImageInto(image, transform, srcRect, bitmap);
+ return copyImageInto(image, srcRect, bitmap);
}
-CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTransform,
- const Rect& srcRect, SkBitmap* bitmap) {
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
+ SkBitmap* bitmap) {
ATRACE_CALL();
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
mRenderThread.requireGlContext();
@@ -303,11 +296,6 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran
CopyResult copyResult = CopyResult::UnknownError;
int displayedWidth = imgWidth, displayedHeight = imgHeight;
- // If this is a 90 or 270 degree rotation we need to swap width/height to get the device
- // size.
- if (texTransform[Matrix4::kSkewX] >= 0.5f || texTransform[Matrix4::kSkewX] <= -0.5f) {
- std::swap(displayedWidth, displayedHeight);
- }
SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height());
SkRect skiaSrcRect = srcRect.toSkRect();
if (skiaSrcRect.isEmpty()) {
@@ -320,7 +308,6 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran
Layer layer(mRenderThread.renderState(), nullptr, 255, SkBlendMode::kSrc);
layer.setSize(displayedWidth, displayedHeight);
- texTransform.copyTo(layer.getTexTransform());
layer.setImage(image);
// Scaling filter is not explicitly set here, because it is done inside copyLayerInfo
// after checking the necessity based on the src/dest rect size and the transformation.
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index da252695dd3b..d0d748ff5c16 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -56,8 +56,7 @@ public:
private:
CopyResult copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
- CopyResult copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTransform,
- const Rect& srcRect, SkBitmap* bitmap);
+ CopyResult copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect, SkBitmap* bitmap);
bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
SkBitmap* bitmap);
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 442ae0fb2707..a285462eef74 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -15,6 +15,7 @@
*/
#include "RecordingCanvas.h"
+#include <hwui/Paint.h>
#include <GrRecordingContext.h>
@@ -186,6 +187,11 @@ struct ClipRegion final : Op {
SkClipOp op;
void draw(SkCanvas* c, const SkMatrix&) const { c->clipRegion(region, op); }
};
+struct ResetClip final : Op {
+ static const auto kType = Type::ResetClip;
+ ResetClip() {}
+ void draw(SkCanvas* c, const SkMatrix&) const { SkAndroidFrameworkUtils::ResetClip(c); }
+};
struct DrawPaint final : Op {
static const auto kType = Type::DrawPaint;
@@ -495,7 +501,7 @@ struct DrawVectorDrawable final : Op {
sp<VectorDrawableRoot> mRoot;
SkRect mBounds;
- SkPaint paint;
+ Paint paint;
BitmapPalette palette;
};
@@ -661,6 +667,9 @@ void DisplayListData::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) {
this->push<ClipRegion>(0, region, op);
}
+void DisplayListData::resetClip() {
+ this->push<ResetClip>(0);
+}
void DisplayListData::drawPaint(const SkPaint& paint) {
this->push<DrawPaint>(0, paint);
@@ -833,7 +842,8 @@ constexpr color_transform_fn colorTransformForOp() {
// TODO: We should be const. Or not. Or just use a different map
// Unclear, but this is the quick fix
const T* op = reinterpret_cast<const T*>(opRaw);
- transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);
+ const SkPaint* paint = &op->paint;
+ transformPaint(transform, const_cast<SkPaint*>(paint), op->palette);
};
}
else if
@@ -842,7 +852,8 @@ constexpr color_transform_fn colorTransformForOp() {
// TODO: We should be const. Or not. Or just use a different map
// Unclear, but this is the quick fix
const T* op = reinterpret_cast<const T*>(opRaw);
- transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));
+ const SkPaint* paint = &op->paint;
+ transformPaint(transform, const_cast<SkPaint*>(paint));
};
}
else {
@@ -966,6 +977,14 @@ void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
fDL->clipRegion(region, op);
this->INHERITED::onClipRegion(region, op);
}
+void RecordingCanvas::onResetClip() {
+ // This is part of "replace op" emulation, but rely on the following intersection
+ // clip to potentially mark the clip as complex. If we are already complex, we do
+ // not reset the complexity so that we don't break the contract that no higher
+ // save point has a complex clip when "not complex".
+ fDL->resetClip();
+ this->INHERITED::onResetClip();
+}
void RecordingCanvas::onDrawPaint(const SkPaint& paint) {
fDL->drawPaint(paint);
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 4fae6a13a25a..212b4e72dcb2 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -97,6 +97,7 @@ private:
void clipRect(const SkRect&, SkClipOp, bool aa);
void clipRRect(const SkRRect&, SkClipOp, bool aa);
void clipRegion(const SkRegion&, SkClipOp);
+ void resetClip();
void drawPaint(const SkPaint&);
void drawBehind(const SkPaint&);
@@ -169,6 +170,7 @@ public:
void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
void onClipRegion(const SkRegion&, SkClipOp) override;
+ void onResetClip() override;
void onDrawPaint(const SkPaint&) override;
void onDrawBehind(const SkPaint&) override;
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index cd622eba37b6..064ba7aee107 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -165,11 +165,11 @@ public:
bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) {
// parent may have already dictated that a descendant layer is needed
bool functorsNeedLayer =
- ancestorDictatesFunctorsNeedLayer
- || CC_UNLIKELY(isClipMayBeComplex())
+ ancestorDictatesFunctorsNeedLayer ||
+ CC_UNLIKELY(isClipMayBeComplex())
// Round rect clipping forces layer for functors
- || CC_UNLIKELY(getOutline().willRoundRectClip()) ||
+ || CC_UNLIKELY(getOutline().willComplexClip()) ||
CC_UNLIKELY(getRevealClip().willClip())
// Complex matrices forces layer, due to stencil clipping
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d032e2b00649..53c6db0cdf3a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -182,7 +182,7 @@ int SkiaCanvas::saveUnclippedLayer(int left, int top, int right, int bottom) {
return SkAndroidFrameworkUtils::SaveBehind(mCanvas, &bounds);
}
-void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const SkPaint& paint) {
+void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const Paint& paint) {
while (mCanvas->getSaveCount() > restoreCount + 1) {
this->restore();
@@ -396,6 +396,22 @@ bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
return !mCanvas->isClipEmpty();
}
+bool SkiaCanvas::replaceClipRect_deprecated(float left, float top, float right, float bottom) {
+ SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+
+ // Emulated clip rects are not recorded for partial saves, since
+ // partial saves have been removed from the public API.
+ SkAndroidFrameworkUtils::ResetClip(mCanvas);
+ mCanvas->clipRect(rect, SkClipOp::kIntersect);
+ return !mCanvas->isClipEmpty();
+}
+
+bool SkiaCanvas::replaceClipPath_deprecated(const SkPath* path) {
+ SkAndroidFrameworkUtils::ResetClip(mCanvas);
+ mCanvas->clipPath(*path, SkClipOp::kIntersect, true);
+ return !mCanvas->isClipEmpty();
+}
+
// ----------------------------------------------------------------------------
// Canvas state operations: Filters
// ----------------------------------------------------------------------------
@@ -439,13 +455,13 @@ void SkiaCanvas::drawColor(int color, SkBlendMode mode) {
mCanvas->drawColor(color, mode);
}
-void SkiaCanvas::onFilterPaint(SkPaint& paint) {
+void SkiaCanvas::onFilterPaint(Paint& paint) {
if (mPaintFilter) {
- mPaintFilter->filter(&paint);
+ mPaintFilter->filterFullPaint(&paint);
}
}
-void SkiaCanvas::drawPaint(const SkPaint& paint) {
+void SkiaCanvas::drawPaint(const Paint& paint) {
mCanvas->drawPaint(filterPaint(paint));
}
@@ -552,9 +568,8 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto image = bitmap.makeImage();
- applyLooper(paint, [&](const SkPaint& p) {
- auto sampling = SkSamplingOptions(p.getFilterQuality());
- mCanvas->drawImage(image, left, top, sampling, &p);
+ applyLooper(paint, [&](const Paint& p) {
+ mCanvas->drawImage(image, left, top, p.sampling(), &p);
});
}
@@ -562,9 +577,8 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint*
auto image = bitmap.makeImage();
SkAutoCanvasRestore acr(mCanvas, true);
mCanvas->concat(matrix);
- applyLooper(paint, [&](const SkPaint& p) {
- auto sampling = SkSamplingOptions(p.getFilterQuality());
- mCanvas->drawImage(image, 0, 0, sampling, &p);
+ applyLooper(paint, [&](const Paint& p) {
+ mCanvas->drawImage(image, 0, 0, p.sampling(), &p);
});
}
@@ -575,18 +589,12 @@ 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);
- applyLooper(paint, [&](const SkPaint& p) {
- auto sampling = SkSamplingOptions(p.getFilterQuality());
- mCanvas->drawImageRect(image, srcRect, dstRect, sampling, &p,
+ applyLooper(paint, [&](const Paint& p) {
+ mCanvas->drawImageRect(image, srcRect, dstRect, p.sampling(), &p,
SkCanvas::kFast_SrcRectConstraint);
});
}
-static SkFilterMode paintToFilter(const Paint* paint) {
- return paint && paint->isFilterBitmap() ? SkFilterMode::kLinear
- : SkFilterMode::kNearest;
-}
-
void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const Paint* paint) {
const int ptCount = (meshWidth + 1) * (meshHeight + 1);
@@ -668,13 +676,13 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
if (paint) {
pnt = *paint;
}
- SkSamplingOptions sampling(paintToFilter(&pnt));
+ SkSamplingOptions sampling = pnt.sampling();
pnt.setShader(image->makeShader(sampling));
auto v = builder.detach();
- applyLooper(&pnt, [&](const SkPaint& p) {
+ applyLooper(&pnt, [&](const Paint& p) {
SkPaint copy(p);
- auto s = SkSamplingOptions(p.getFilterQuality());
+ auto s = p.sampling();
if (s != sampling) {
// applyLooper changed the quality?
copy.setShader(image->makeShader(s));
@@ -707,9 +715,8 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
auto image = bitmap.makeImage();
- applyLooper(paint, [&](const SkPaint& p) {
- auto filter = SkSamplingOptions(p.getFilterQuality()).filter;
- mCanvas->drawImageLattice(image.get(), lattice, dst, filter, &p);
+ applyLooper(paint, [&](const Paint& p) {
+ mCanvas->drawImageLattice(image.get(), lattice, dst, p.filterMode(), &p);
});
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 438a40cb4c81..715007cdcd3b 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -23,8 +23,10 @@
#include "VectorDrawable.h"
#include "hwui/Canvas.h"
#include "hwui/Paint.h"
+#include "hwui/BlurDrawLooper.h"
#include <SkCanvas.h>
+#include <SkDeque.h>
#include "pipeline/skia/AnimatedDrawables.h"
#include "src/core/SkArenaAlloc.h"
@@ -73,7 +75,7 @@ public:
virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
- virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) override;
+ virtual void restoreUnclippedLayer(int saveCount, const Paint& paint) override;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) override;
@@ -92,6 +94,9 @@ public:
virtual bool quickRejectPath(const SkPath& path) const override;
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
virtual bool clipPath(const SkPath* path, SkClipOp op) override;
+ virtual bool replaceClipRect_deprecated(float left, float top, float right,
+ float bottom) override;
+ virtual bool replaceClipPath_deprecated(const SkPath* path) override;
virtual PaintFilter* getPaintFilter() override;
virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) override;
@@ -99,7 +104,7 @@ public:
virtual SkCanvasState* captureCanvasState() const override;
virtual void drawColor(int color, SkBlendMode mode) override;
- virtual void drawPaint(const SkPaint& paint) override;
+ virtual void drawPaint(const Paint& paint) override;
virtual void drawPoint(float x, float y, const Paint& paint) override;
virtual void drawPoints(const float* points, int count, const Paint& paint) override;
@@ -167,10 +172,10 @@ protected:
const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
- void onFilterPaint(SkPaint& paint);
+ void onFilterPaint(Paint& paint);
- SkPaint filterPaint(const SkPaint& src) {
- SkPaint dst(src);
+ Paint filterPaint(const Paint& src) {
+ Paint dst(src);
this->onFilterPaint(dst);
return dst;
}
@@ -179,21 +184,20 @@ protected:
template <typename Proc>
void applyLooper(const Paint* paint, Proc proc, void (*preFilter)(SkPaint&) = nullptr) {
BlurDrawLooper* looper = paint ? paint->getLooper() : nullptr;
- const SkPaint* skpPtr = paint;
- SkPaint skp = skpPtr ? *skpPtr : SkPaint();
+ Paint pnt = paint ? *paint : Paint();
if (preFilter) {
- preFilter(skp);
+ preFilter(pnt);
}
- this->onFilterPaint(skp);
+ this->onFilterPaint(pnt);
if (looper) {
- looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+ looper->apply(pnt, [&](SkPoint offset, const Paint& modifiedPaint) {
mCanvas->save();
mCanvas->translate(offset.fX, offset.fY);
proc(modifiedPaint);
mCanvas->restore();
});
} else {
- proc(skp);
+ proc(pnt);
}
}
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index cc9094c8afe9..6b8f43946a74 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -100,6 +100,8 @@ public:
int stretchEffectCount = 0;
+ bool forceDrawFrame = false;
+
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 55f434f49bbd..983c7766273a 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -269,7 +269,7 @@ void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value)
void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
SkPath tempStagingPath;
- outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
+ outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath), true);
}
Group::Group(const Group& group) : Node(group) {
@@ -463,10 +463,10 @@ void Tree::drawStaging(Canvas* outCanvas) {
mStagingCache.dirty = false;
}
- SkPaint skp;
+ Paint skp;
getPaintFor(&skp, mStagingProperties);
Paint paint;
- paint.setFilterQuality(skp.getFilterQuality());
+ paint.setFilterBitmap(skp.isFilterBitmap());
paint.setColorFilter(skp.refColorFilter());
paint.setAlpha(skp.getAlpha());
outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0, mStagingCache.bitmap->width(),
@@ -476,9 +476,9 @@ void Tree::drawStaging(Canvas* outCanvas) {
mStagingProperties.getBounds().bottom(), &paint);
}
-void Tree::getPaintFor(SkPaint* outPaint, const TreeProperties& prop) const {
+void Tree::getPaintFor(Paint* outPaint, const TreeProperties& prop) const {
// HWUI always draws VD with bilinear filtering.
- outPaint->setFilterQuality(kLow_SkFilterQuality);
+ outPaint->setFilterBitmap(true);
if (prop.getColorFilter() != nullptr) {
outPaint->setColorFilter(sk_ref_sp(prop.getColorFilter()));
}
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index ac7d41e0d600..30bb04ae8361 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -648,7 +648,7 @@ public:
*/
void draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& paint);
- void getPaintFor(SkPaint* outPaint, const TreeProperties &props) const;
+ void getPaintFor(Paint* outPaint, const TreeProperties &props) const;
BitmapPalette computePalette();
void setAntiAlias(bool aa) { mRootNode->setAntiAlias(aa); }
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 5aad821ad59f..6fc251dc815c 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -118,6 +118,24 @@ void WebViewFunctor::onRemovedFromTree() {
}
}
+bool WebViewFunctor::prepareRootSurfaceControl() {
+ if (!Properties::enableWebViewOverlays) return false;
+
+ renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext();
+ if (!activeContext) return false;
+
+ ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
+ if (!rootSurfaceControl) return false;
+
+ int32_t rgid = activeContext->getSurfaceControlGenerationId();
+ if (mParentSurfaceControlGenerationId != rgid) {
+ reparentSurfaceControl(rootSurfaceControl);
+ mParentSurfaceControlGenerationId = rgid;
+ }
+
+ return true;
+}
+
void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {
ATRACE_NAME("WebViewFunctor::drawGl");
if (!mHasContext) {
@@ -131,20 +149,8 @@ void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {
.mergeTransaction = currentFunctor.mergeTransaction,
};
- if (Properties::enableWebViewOverlays && !drawInfo.isLayer) {
- renderthread::CanvasContext* activeContext =
- renderthread::CanvasContext::getActiveContext();
- if (activeContext != nullptr) {
- ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
- if (rootSurfaceControl) {
- overlayParams.overlaysMode = OverlaysMode::Enabled;
- int32_t rgid = activeContext->getSurfaceControlGenerationId();
- if (mParentSurfaceControlGenerationId != rgid) {
- reparentSurfaceControl(rootSurfaceControl);
- mParentSurfaceControlGenerationId = rgid;
- }
- }
- }
+ if (!drawInfo.isLayer && prepareRootSurfaceControl()) {
+ overlayParams.overlaysMode = OverlaysMode::Enabled;
}
mCallbacks.gles.draw(mFunctor, mData, drawInfo, overlayParams);
@@ -170,7 +176,10 @@ void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {
.mergeTransaction = currentFunctor.mergeTransaction,
};
- // TODO, enable surface control once offscreen mode figured out
+ if (!params.is_layer && prepareRootSurfaceControl()) {
+ overlayParams.overlaysMode = OverlaysMode::Enabled;
+ }
+
mCallbacks.vk.draw(mFunctor, mData, params, overlayParams);
}
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index f28f310993ec..0a02f2d4b720 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -88,6 +88,7 @@ public:
}
private:
+ bool prepareRootSurfaceControl();
void reparentSurfaceControl(ASurfaceControl* parent);
private:
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index dca10e29cbb8..942c0506321c 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -14,31 +14,22 @@
* limitations under the License.
*/
-#include "graphics_jni_helpers.h"
-
#include <GraphicsJNI.h>
#include <SkGraphics.h>
-#include <sstream>
-#include <iostream>
-#include <unicode/putil.h>
#include <unordered_map>
#include <vector>
-using namespace std;
-
-/*
- * This is responsible for setting up the JNI environment for communication between
- * the Java and native parts of layoutlib, including registering native methods.
- * This is mostly achieved by copying the way it is done in the platform
- * (see AndroidRuntime.cpp).
- */
+#include "Properties.h"
+#include "android/graphics/jni_runtime.h"
+#include "graphics_jni_helpers.h"
-static JavaVM* javaVM;
+using namespace std;
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
+extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
@@ -49,10 +40,12 @@ extern int register_android_graphics_PathEffect(JNIEnv* env);
extern int register_android_graphics_Shader(JNIEnv* env);
extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
+extern int register_android_graphics_YuvImage(JNIEnv* env);
namespace android {
extern int register_android_graphics_Canvas(JNIEnv* env);
+extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
@@ -62,7 +55,7 @@ extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv* env);
-//extern int register_android_graphics_Region(JNIEnv* env);
+extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env);
extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env);
extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
@@ -71,9 +64,11 @@ extern int register_android_graphics_fonts_Font(JNIEnv* env);
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
+extern int register_android_graphics_text_TextShaper(JNIEnv* env);
+
extern int register_android_util_PathParser(JNIEnv* env);
-extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_RenderNode(JNIEnv* env);
#define REG_JNI(name) { name }
struct RegJNIRec {
@@ -87,8 +82,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
{"android.graphics.ByteBufferStreamAdaptor",
REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
+ {"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
{"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
- {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
+ {"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)},
{"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
{"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
{"android.graphics.CreateJavaOutputStreamAdaptor",
@@ -107,10 +103,12 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
{"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
{"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
-// {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
+ {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
+ {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
{"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
{"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)},
{"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
+ {"android.graphics.YuvImage", REG_JNI(register_android_graphics_YuvImage)},
{"android.graphics.animation.NativeInterpolatorFactory",
REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
{"android.graphics.animation.RenderNodeAnimator",
@@ -124,6 +122,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
{"android.graphics.text.MeasuredText",
REG_JNI(register_android_graphics_text_MeasuredText)},
+ {"android.graphics.text.TextRunShaper", REG_JNI(register_android_graphics_text_TextShaper)},
{"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
};
@@ -177,10 +176,9 @@ int register_android_graphics_classes(JNIEnv *env) {
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
// Get the names of classes that need to register their native methods
- auto nativesClassesJString =
- (jstring) env->CallStaticObjectMethod(system,
- getPropertyMethod, env->NewStringUTF("native_classes"),
- env->NewStringUTF(""));
+ auto nativesClassesJString = (jstring)env->CallStaticObjectMethod(
+ system, getPropertyMethod, env->NewStringUTF("graphics_native_classes"),
+ env->NewStringUTF(""));
vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index 3780ba072308..bc6bc456ba5a 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -57,6 +57,8 @@ static AndroidBitmapFormat getFormat(const SkImageInfo& info) {
return ANDROID_BITMAP_FORMAT_A_8;
case kRGBA_F16_SkColorType:
return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_1010102;
default:
return ANDROID_BITMAP_FORMAT_NONE;
}
@@ -74,6 +76,8 @@ static SkColorType getColorType(AndroidBitmapFormat format) {
return kAlpha_8_SkColorType;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return kRGBA_F16_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return kRGBA_1010102_SkColorType;
default:
return kUnknown_SkColorType;
}
@@ -249,6 +253,9 @@ int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const
case ANDROID_BITMAP_FORMAT_RGBA_F16:
colorType = kRGBA_F16_SkColorType;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ colorType = kRGBA_1010102_SkColorType;
+ break;
default:
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
diff --git a/libs/hwui/apex/include/android/graphics/renderthread.h b/libs/hwui/apex/include/android/graphics/renderthread.h
deleted file mode 100644
index 50280a6dd1fb..000000000000
--- a/libs/hwui/apex/include/android/graphics/renderthread.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_GRAPHICS_RENDERTHREAD_H
-#define ANDROID_GRAPHICS_RENDERTHREAD_H
-
-#include <cutils/compiler.h>
-#include <sys/cdefs.h>
-
-__BEGIN_DECLS
-
-/**
- * Dumps a textual representation of the graphics stats for this process.
- * @param fd The file descriptor that the available graphics stats will be appended to. The
- * function requires a valid fd, but does not persist or assume ownership of the fd
- * outside the scope of this function.
- */
-ANDROID_API void ARenderThread_dumpGraphicsMemory(int fd);
-
-__END_DECLS
-
-#endif // ANDROID_GRAPHICS_RENDERTHREAD_H
diff --git a/libs/hwui/apex/renderthread.cpp b/libs/hwui/apex/renderthread.cpp
deleted file mode 100644
index 5d26afe7a2ab..000000000000
--- a/libs/hwui/apex/renderthread.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "android/graphics/renderthread.h"
-
-#include <renderthread/RenderProxy.h>
-
-using namespace android;
-
-void ARenderThread_dumpGraphicsMemory(int fd) {
- uirenderer::renderthread::RenderProxy::dumpGraphicsMemory(fd);
-}
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
index 8f261c83b8d3..112ac124ffa1 100644
--- a/libs/hwui/canvas/CanvasFrontend.cpp
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -30,44 +30,61 @@ void CanvasStateHelper::resetState(int width, int height) {
mClipStack.clear();
mTransformStack.clear();
mSaveStack.emplace_back();
- mClipStack.emplace_back().setRect(mInitialBounds);
+ mClipStack.emplace_back();
mTransformStack.emplace_back();
- mCurrentClipIndex = 0;
- mCurrentTransformIndex = 0;
+
+ clip().bounds = mInitialBounds;
}
bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
mSaveStack.push_back(saveEntry);
if (saveEntry.matrix) {
- // We need to push before accessing transform() to ensure the reference doesn't move
- // across vector resizes
- mTransformStack.emplace_back() = transform();
- mCurrentTransformIndex += 1;
+ pushEntry(&mTransformStack);
}
if (saveEntry.clip) {
- // We need to push before accessing clip() to ensure the reference doesn't move
- // across vector resizes
- mClipStack.emplace_back() = clip();
- mCurrentClipIndex += 1;
+ pushEntry(&mClipStack);
return true;
}
return false;
}
-// Assert that the cast from SkClipOp to SkRegion::Op is valid
-static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
-static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
-static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
-static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
-static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
-static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);
+void CanvasStateHelper::ConservativeClip::apply(SkClipOp op, const SkMatrix& matrix,
+ const SkRect& bounds, bool aa, bool fillsBounds) {
+ this->aa |= aa;
+
+ if (op == SkClipOp::kIntersect) {
+ SkRect devBounds;
+ bool rect = matrix.mapRect(&devBounds, bounds) && fillsBounds;
+ if (!this->bounds.intersect(aa ? devBounds.roundOut() : devBounds.round())) {
+ this->bounds.setEmpty();
+ }
+ this->rect &= rect;
+ } else {
+ // Difference operations subtracts a region from the clip, so conservatively
+ // the bounds remain unchanged and the shape is unlikely to remain a rect.
+ this->rect = false;
+ }
+}
void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
- clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
+ clip().apply(op, transform(), rect, /*aa=*/false, /*fillsBounds=*/true);
}
void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
- clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
+ SkRect bounds = path.getBounds();
+ if (path.isInverseFillType()) {
+ // Toggle op type if the path is inverse filled
+ op = (op == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect);
+ }
+ clip().apply(op, transform(), bounds, /*aa=*/true, /*fillsBounds=*/false);
+}
+
+CanvasStateHelper::ConservativeClip& CanvasStateHelper::clip() {
+ return writableEntry(&mClipStack);
+}
+
+SkMatrix& CanvasStateHelper::transform() {
+ return writableEntry(&mTransformStack);
}
bool CanvasStateHelper::internalRestore() {
@@ -80,45 +97,47 @@ bool CanvasStateHelper::internalRestore() {
mSaveStack.pop_back();
bool needsRestorePropagation = entry.layer;
if (entry.matrix) {
- mTransformStack.pop_back();
- mCurrentTransformIndex -= 1;
+ popEntry(&mTransformStack);
}
if (entry.clip) {
- // We need to push before accessing clip() to ensure the reference doesn't move
- // across vector resizes
- mClipStack.pop_back();
- mCurrentClipIndex -= 1;
+ popEntry(&mClipStack);
needsRestorePropagation = true;
}
return needsRestorePropagation;
}
SkRect CanvasStateHelper::getClipBounds() const {
- SkIRect ibounds = clip().getBounds();
-
- if (ibounds.isEmpty()) {
- return SkRect::MakeEmpty();
- }
+ SkIRect bounds = clip().bounds;
SkMatrix inverse;
// if we can't invert the CTM, we can't return local clip bounds
- if (!transform().invert(&inverse)) {
+ if (bounds.isEmpty() || !transform().invert(&inverse)) {
return SkRect::MakeEmpty();
}
- SkRect ret = SkRect::MakeEmpty();
- inverse.mapRect(&ret, SkRect::Make(ibounds));
- return ret;
+ return inverse.mapRect(SkRect::Make(bounds));
+}
+
+bool CanvasStateHelper::ConservativeClip::quickReject(const SkMatrix& matrix,
+ const SkRect& bounds) const {
+ SkRect devRect = matrix.mapRect(bounds);
+ return devRect.isFinite() &&
+ SkIRect::Intersects(this->bounds, aa ? devRect.roundOut() : devRect.round());
}
bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
- // TODO: Implement
- return false;
+ return clip().quickReject(transform(), SkRect::MakeLTRB(left, top, right, bottom));
}
bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
- // TODO: Implement
- return false;
+ if (this->isClipEmpty()) {
+ // reject everything (prioritized above path inverse fill type).
+ return true;
+ } else {
+ // Don't reject inverse-filled paths, since even if they are "empty" of points/verbs,
+ // they fill out the entire clip.
+ return !path.isInverseFillType() && clip().quickReject(transform(), path.getBounds());
+ }
}
} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
index f9a610129d3a..9f22b900e4ab 100644
--- a/libs/hwui/canvas/CanvasFrontend.h
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -21,7 +21,6 @@
#include "CanvasOpBuffer.h"
#include <SaveFlags.h>
-#include <SkRasterClip.h>
#include <ui/FatVector.h>
#include <optional>
@@ -40,6 +39,26 @@ protected:
bool layer : 1 = false;
};
+ template <typename T>
+ struct DeferredEntry {
+ T entry;
+ int deferredSaveCount = 0;
+
+ DeferredEntry() = default;
+ DeferredEntry(const T& t) : entry(t) {}
+ };
+
+ struct ConservativeClip {
+ SkIRect bounds = SkIRect::MakeEmpty();
+ bool rect = true;
+ bool aa = false;
+
+ bool quickReject(const SkMatrix& matrix, const SkRect& bounds) const;
+
+ void apply(SkClipOp op, const SkMatrix& matrix, const SkRect& bounds, bool aa,
+ bool fillsBounds);
+ };
+
constexpr SaveEntry saveEntryForLayer() {
return {
.clip = true,
@@ -72,23 +91,47 @@ protected:
void internalClipRect(const SkRect& rect, SkClipOp op);
void internalClipPath(const SkPath& path, SkClipOp op);
+ // The canvas' clip will never expand beyond these bounds since intersect
+ // and difference operations only subtract pixels.
SkIRect mInitialBounds;
+ // Every save() gets a SaveEntry to track what needs to be restored.
FatVector<SaveEntry, 6> mSaveStack;
- FatVector<SkMatrix, 6> mTransformStack;
- FatVector<SkConservativeClip, 6> mClipStack;
+ // Transform and clip entries record a deferred save count and do not
+ // make a new entry until that particular state is modified.
+ FatVector<DeferredEntry<SkMatrix>, 6> mTransformStack;
+ FatVector<DeferredEntry<ConservativeClip>, 6> mClipStack;
- size_t mCurrentTransformIndex;
- size_t mCurrentClipIndex;
+ const ConservativeClip& clip() const { return mClipStack.back().entry; }
- const SkConservativeClip& clip() const {
- return mClipStack[mCurrentClipIndex];
+ ConservativeClip& clip();
+
+ void resetState(int width, int height);
+
+ // Stack manipulation for transform and clip stacks
+ template <typename T, size_t N>
+ void pushEntry(FatVector<DeferredEntry<T>, N>* stack) {
+ stack->back().deferredSaveCount += 1;
}
- SkConservativeClip& clip() {
- return mClipStack[mCurrentClipIndex];
+ template <typename T, size_t N>
+ void popEntry(FatVector<DeferredEntry<T>, N>* stack) {
+ if (!(stack->back().deferredSaveCount--)) {
+ stack->pop_back();
+ }
}
- void resetState(int width, int height);
+ template <typename T, size_t N>
+ T& writableEntry(FatVector<DeferredEntry<T>, N>* stack) {
+ DeferredEntry<T>& back = stack->back();
+ if (back.deferredSaveCount == 0) {
+ return back.entry;
+ } else {
+ back.deferredSaveCount -= 1;
+ // saved in case references move when re-allocating vector storage
+ T state = back.entry;
+ return stack->emplace_back(state).entry;
+ }
+ }
public:
int saveCount() const { return mSaveStack.size(); }
@@ -97,13 +140,14 @@ public:
bool quickRejectRect(float left, float top, float right, float bottom) const;
bool quickRejectPath(const SkPath& path) const;
- const SkMatrix& transform() const {
- return mTransformStack[mCurrentTransformIndex];
- }
+ bool isClipAA() const { return clip().aa; }
+ bool isClipEmpty() const { return clip().bounds.isEmpty(); }
+ bool isClipRect() const { return clip().rect; }
+ bool isClipComplex() const { return !isClipEmpty() && (isClipAA() || !isClipRect()); }
- SkMatrix& transform() {
- return mTransformStack[mCurrentTransformIndex];
- }
+ const SkMatrix& transform() const { return mTransformStack.back().entry; }
+
+ SkMatrix& transform();
// For compat with existing HWUI Canvas interface
void getMatrix(SkMatrix* outMatrix) const {
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 17cd3ceb577c..2757c3952dbb 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -181,7 +181,7 @@ static const SkString stretchShader = SkString(R"(
);
coord.x = outU;
coord.y = outV;
- return sample(uContentTexture, coord);
+ return uContentTexture.eval(coord);
})");
static const float ZERO = 0.f;
@@ -227,7 +227,7 @@ sk_sp<SkShader> StretchEffect::getShader(float width, float height,
mBuilder->uniform("viewportWidth").set(&width, 1);
mBuilder->uniform("viewportHeight").set(&height, 1);
- auto result = mBuilder->makeShader(nullptr, false);
+ auto result = mBuilder->makeShader();
mBuilder->child(CONTENT_TEXTURE) = nullptr;
return result;
}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 876f5c895c60..d08bc5c583c2 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -157,7 +157,6 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
lazyPaint.emplace();
lazyPaint->setAlpha(mProperties.mAlpha);
lazyPaint->setColorFilter(mProperties.mColorFilter);
- lazyPaint->setFilterQuality(kLow_SkFilterQuality);
}
canvas->concat(matrix);
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 1a89cfd5d0ad..67f47580a70f 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -104,6 +104,10 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info,
sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) {
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
+ if (bitmap.colorType() == kAlpha_8_SkColorType &&
+ !uirenderer::HardwareBitmapUploader::hasAlpha8Support()) {
+ return nullptr;
+ }
return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap);
#else
return Bitmap::allocateHeapBitmap(bitmap.info());
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
index 27a038d4598e..270d24af99fd 100644
--- a/libs/hwui/hwui/BlurDrawLooper.cpp
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -24,7 +24,7 @@ BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset)
BlurDrawLooper::~BlurDrawLooper() = default;
-SkPoint BlurDrawLooper::apply(SkPaint* paint) const {
+SkPoint BlurDrawLooper::apply(Paint* paint) const {
paint->setColor(mColor);
if (mBlurSigma > 0) {
paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true));
diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h
index 7e6786f7dfbc..09a4e0f849b0 100644
--- a/libs/hwui/hwui/BlurDrawLooper.h
+++ b/libs/hwui/hwui/BlurDrawLooper.h
@@ -17,7 +17,7 @@
#ifndef ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
-#include <SkPaint.h>
+#include <hwui/Paint.h>
#include <SkRefCnt.h>
class SkColorSpace;
@@ -30,10 +30,10 @@ public:
~BlurDrawLooper() override;
- // proc(SkPoint offset, const SkPaint& modifiedPaint)
+ // proc(SkPoint offset, const Paint& modifiedPaint)
template <typename DrawProc>
- void apply(const SkPaint& paint, DrawProc proc) const {
- SkPaint p(paint);
+ void apply(const Paint& paint, DrawProc proc) const {
+ Paint p(paint);
proc(this->apply(&p), p); // draw the shadow
proc({0, 0}, paint); // draw the original (on top)
}
@@ -43,7 +43,7 @@ private:
const float mBlurSigma;
const SkPoint mOffset;
- SkPoint apply(SkPaint* paint) const;
+ SkPoint apply(Paint* paint) const;
BlurDrawLooper(SkColor4f, float, SkPoint);
};
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9023613478fc..82777646f3a2 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -162,7 +162,7 @@ public:
virtual int save(SaveFlags::Flags flags) = 0;
virtual void restore() = 0;
virtual void restoreToCount(int saveCount) = 0;
- virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) = 0;
+ virtual void restoreUnclippedLayer(int saveCount, const Paint& paint) = 0;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint) = 0;
virtual int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) = 0;
@@ -185,6 +185,12 @@ public:
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) = 0;
virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
+ // Resets clip to wide open, used to emulate the now-removed SkClipOp::kReplace on
+ // apps with compatibility < P. Canvases for version P and later are restricted to
+ // intersect and difference at the Java level, matching SkClipOp's definition.
+ // NOTE: These functions are deprecated and will be removed in a future release
+ virtual bool replaceClipRect_deprecated(float left, float top, float right, float bottom) = 0;
+ virtual bool replaceClipPath_deprecated(const SkPath* path) = 0;
// filters
virtual PaintFilter* getPaintFilter() = 0;
@@ -197,7 +203,7 @@ public:
// Canvas draw operations
// ----------------------------------------------------------------------------
virtual void drawColor(int color, SkBlendMode mode) = 0;
- virtual void drawPaint(const SkPaint& paint) = 0;
+ virtual void drawPaint(const Paint& paint) = 0;
// Geometry
virtual void drawPoint(float x, float y, const Paint& paint) = 0;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 5d9fad5b676e..dd68f825b61d 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -24,7 +24,6 @@
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkEncodedOrigin.h>
-#include <SkFilterQuality.h>
#include <SkPaint.h>
#undef LOG_TAG
@@ -160,6 +159,8 @@ bool ImageDecoder::setOutColorType(SkColorType colorType) {
break;
case kRGBA_F16_SkColorType:
break;
+ case kRGBA_1010102_SkColorType:
+ break;
default:
return false;
}
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index b8029087cb4f..e359145feef7 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -95,6 +95,16 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
endHyphen, advances);
}
+minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
+ const Typeface* typeface, const uint16_t* buf,
+ size_t start, size_t count, size_t bufSize) {
+ minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
+ const minikin::U16StringPiece textBuf(buf, bufSize);
+ const minikin::Range range(start, start + count);
+
+ return minikin::getFontExtent(textBuf, range, bidiFlags, minikinPaint);
+}
+
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index a15803ad2dca..009b84b140ea 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -56,6 +56,10 @@ public:
size_t start, size_t count, size_t bufSize,
float* advances);
+ static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
+ const Typeface* typeface, const uint16_t* buf,
+ size_t start, size_t count, size_t bufSize);
+
static bool hasVariationSelector(const Typeface* typeface, uint32_t codepoint,
uint32_t vs);
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index d9c9eeed03e9..4a8f3e10fc26 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,13 +17,13 @@
#ifndef ANDROID_GRAPHICS_PAINT_H_
#define ANDROID_GRAPHICS_PAINT_H_
-#include "BlurDrawLooper.h"
#include "Typeface.h"
#include <cutils/compiler.h>
#include <SkFont.h>
#include <SkPaint.h>
+#include <SkSamplingOptions.h>
#include <string>
#include <minikin/FontFamily.h>
@@ -32,6 +32,8 @@
namespace android {
+class BlurDrawLooper;
+
class Paint : public SkPaint {
public:
// Default values for underlined and strikethrough text,
@@ -60,7 +62,7 @@ public:
const SkFont& getSkFont() const { return mFont; }
BlurDrawLooper* getLooper() const { return mLooper.get(); }
- void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); }
+ void setLooper(sk_sp<BlurDrawLooper> looper);
// These shadow the methods on SkPaint, but we need to so we can keep related
// attributes in-sync.
@@ -138,7 +140,15 @@ public:
void setDevKern(bool d) { mDevKern = d; }
// Deprecated -- bitmapshaders will be taking this flag explicitly
- bool isFilterBitmap() const { return this->getFilterQuality() != kNone_SkFilterQuality; }
+ bool isFilterBitmap() const { return mFilterBitmap; }
+ void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
+
+ SkFilterMode filterMode() const {
+ return mFilterBitmap ? SkFilterMode::kLinear : SkFilterMode::kNearest;
+ }
+ SkSamplingOptions sampling() const {
+ return SkSamplingOptions(this->filterMode());
+ }
// The Java flags (Paint.java) no longer fit into the native apis directly.
// These methods handle converting to and from them and the native representations
@@ -169,6 +179,7 @@ private:
// nullptr is valid: it means the default typeface.
const Typeface* mTypeface = nullptr;
Align mAlign = kLeft_Align;
+ bool mFilterBitmap = false;
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
index 0e7b61977000..4996aa445316 100644
--- a/libs/hwui/hwui/PaintFilter.h
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -1,17 +1,18 @@
#ifndef ANDROID_GRAPHICS_PAINT_FILTER_H_
#define ANDROID_GRAPHICS_PAINT_FILTER_H_
-class SkPaint;
+#include <SkRefCnt.h>
namespace android {
+class Paint;
+
class PaintFilter : public SkRefCnt {
public:
/**
* Called with the paint that will be used to draw.
* The implementation may modify the paint as they wish.
*/
- virtual void filter(SkPaint*) = 0;
virtual void filterFullPaint(Paint*) = 0;
};
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index fa2674fc2f5e..aac928f85924 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -15,6 +15,7 @@
*/
#include "Paint.h"
+#include "BlurDrawLooper.h"
namespace android {
@@ -43,6 +44,7 @@ Paint::Paint(const Paint& paint)
, mHyphenEdit(paint.mHyphenEdit)
, mTypeface(paint.mTypeface)
, mAlign(paint.mAlign)
+ , mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
, mDevKern(paint.mDevKern) {}
@@ -62,6 +64,7 @@ Paint& Paint::operator=(const Paint& other) {
mHyphenEdit = other.mHyphenEdit;
mTypeface = other.mTypeface;
mAlign = other.mAlign;
+ mFilterBitmap = other.mFilterBitmap;
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
@@ -77,6 +80,7 @@ bool operator==(const Paint& a, const Paint& b) {
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
+ a.mFilterBitmap == b.mFilterBitmap &&
a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
a.mDevKern == b.mDevKern;
}
@@ -88,11 +92,16 @@ void Paint::reset() {
mFont.setEdging(SkFont::Edging::kAlias);
mLooper.reset();
+ mFilterBitmap = false;
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
}
+void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
+ mLooper = std::move(looper);
+}
+
void Paint::setAntiAlias(bool aa) {
// Java does not support/understand subpixel(lcd) antialiasing
SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias);
@@ -131,9 +140,6 @@ static uint32_t paintToLegacyFlags(const SkPaint& paint) {
uint32_t flags = 0;
flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag;
flags |= -(int)paint.isDither() & sDitherFlag;
- if (paint.getFilterQuality() != kNone_SkFilterQuality) {
- flags |= sFilterBitmapFlag;
- }
return flags;
}
@@ -150,12 +156,6 @@ static uint32_t fontToLegacyFlags(const SkFont& font) {
static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) {
paint->setAntiAlias((flags & sAntiAliasFlag) != 0);
paint->setDither ((flags & sDitherFlag) != 0);
-
- if (flags & sFilterBitmapFlag) {
- paint->setFilterQuality(kLow_SkFilterQuality);
- } else {
- paint->setFilterQuality(kNone_SkFilterQuality);
- }
}
static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) {
@@ -182,18 +182,20 @@ void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) {
uint32_t Paint::getJavaFlags() const {
uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont);
- flags |= -(int)mStrikeThru & sStrikeThruFlag;
- flags |= -(int)mUnderline & sUnderlineFlag;
- flags |= -(int)mDevKern & sDevKernFlag;
+ flags |= -(int)mStrikeThru & sStrikeThruFlag;
+ flags |= -(int)mUnderline & sUnderlineFlag;
+ flags |= -(int)mDevKern & sDevKernFlag;
+ flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
return flags;
}
void Paint::setJavaFlags(uint32_t flags) {
applyLegacyFlagsToPaint(flags, this);
applyLegacyFlagsToFont(flags, &mFont);
- mStrikeThru = (flags & sStrikeThruFlag) != 0;
- mUnderline = (flags & sUnderlineFlag) != 0;
- mDevKern = (flags & sDevKernFlag) != 0;
+ mStrikeThru = (flags & sStrikeThruFlag) != 0;
+ mUnderline = (flags & sUnderlineFlag) != 0;
+ mDevKern = (flags & sDevKernFlag) != 0;
+ mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
}
} // namespace android
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index c9433ec8a9da..c40b858268be 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -30,7 +30,8 @@
using namespace android;
-static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID;
+static jclass gAnimatedImageDrawableClass;
+static jmethodID gAnimatedImageDrawable_callOnAnimationEndMethodID;
// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -178,26 +179,23 @@ class InvokeListener : public MessageHandler {
public:
InvokeListener(JNIEnv* env, jobject javaObject) {
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK);
- // Hold a weak reference to break a cycle that would prevent GC.
- mWeakRef = env->NewWeakGlobalRef(javaObject);
+ mCallbackRef = env->NewGlobalRef(javaObject);
}
~InvokeListener() override {
auto* env = requireEnv(mJvm);
- env->DeleteWeakGlobalRef(mWeakRef);
+ env->DeleteGlobalRef(mCallbackRef);
}
virtual void handleMessage(const Message&) override {
auto* env = get_env_or_die(mJvm);
- jobject localRef = env->NewLocalRef(mWeakRef);
- if (localRef) {
- env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID);
- }
+ env->CallStaticVoidMethod(gAnimatedImageDrawableClass,
+ gAnimatedImageDrawable_callOnAnimationEndMethodID, mCallbackRef);
}
private:
JavaVM* mJvm;
- jweak mWeakRef;
+ jobject mCallbackRef;
};
class JniAnimationEndListener : public OnAnimationEndListener {
@@ -253,26 +251,31 @@ static void AnimatedImageDrawable_nSetBounds(JNIEnv* env, jobject /*clazz*/, jlo
}
static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
- { "nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",(void*) AnimatedImageDrawable_nCreate },
- { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer },
- { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw },
- { "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha },
- { "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha },
- { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
- { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning },
- { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart },
- { "nStop", "(J)Z", (void*) AnimatedImageDrawable_nStop },
- { "nGetRepeatCount", "(J)I", (void*) AnimatedImageDrawable_nGetRepeatCount },
- { "nSetRepeatCount", "(JI)V", (void*) AnimatedImageDrawable_nSetRepeatCount },
- { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
- { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
- { "nSetMirrored", "(JZ)V", (void*) AnimatedImageDrawable_nSetMirrored },
- { "nSetBounds", "(JLandroid/graphics/Rect;)V", (void*) AnimatedImageDrawable_nSetBounds },
+ {"nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",
+ (void*)AnimatedImageDrawable_nCreate},
+ {"nGetNativeFinalizer", "()J", (void*)AnimatedImageDrawable_nGetNativeFinalizer},
+ {"nDraw", "(JJ)J", (void*)AnimatedImageDrawable_nDraw},
+ {"nSetAlpha", "(JI)V", (void*)AnimatedImageDrawable_nSetAlpha},
+ {"nGetAlpha", "(J)I", (void*)AnimatedImageDrawable_nGetAlpha},
+ {"nSetColorFilter", "(JJ)V", (void*)AnimatedImageDrawable_nSetColorFilter},
+ {"nIsRunning", "(J)Z", (void*)AnimatedImageDrawable_nIsRunning},
+ {"nStart", "(J)Z", (void*)AnimatedImageDrawable_nStart},
+ {"nStop", "(J)Z", (void*)AnimatedImageDrawable_nStop},
+ {"nGetRepeatCount", "(J)I", (void*)AnimatedImageDrawable_nGetRepeatCount},
+ {"nSetRepeatCount", "(JI)V", (void*)AnimatedImageDrawable_nSetRepeatCount},
+ {"nSetOnAnimationEndListener", "(JLjava/lang/ref/WeakReference;)V",
+ (void*)AnimatedImageDrawable_nSetOnAnimationEndListener},
+ {"nNativeByteSize", "(J)J", (void*)AnimatedImageDrawable_nNativeByteSize},
+ {"nSetMirrored", "(JZ)V", (void*)AnimatedImageDrawable_nSetMirrored},
+ {"nSetBounds", "(JLandroid/graphics/Rect;)V", (void*)AnimatedImageDrawable_nSetBounds},
};
int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
- jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable");
- gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V");
+ gAnimatedImageDrawableClass = reinterpret_cast<jclass>(env->NewGlobalRef(
+ FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable")));
+ gAnimatedImageDrawable_callOnAnimationEndMethodID =
+ GetStaticMethodIDOrDie(env, gAnimatedImageDrawableClass, "callOnAnimationEnd",
+ "(Ljava/lang/ref/WeakReference;)V");
return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 4003f0b65fb5..5db0783cf83e 100755
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -884,7 +884,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
jlong bitmapHandle, jint density, jobject parcel) {
#ifdef __ANDROID__ // Layoutlib does not support parcel
if (parcel == NULL) {
- SkDebugf("------- writeToParcel null parcel\n");
+ ALOGD("------- writeToParcel null parcel\n");
return JNI_FALSE;
}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4cc05ef6f13b..1c20415dcc8f 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -137,9 +137,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
SkColorType decodeColorType = brd->computeOutputColorType(colorType);
- if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+
+ if (isHardware) {
+ if (decodeColorType == kRGBA_F16_SkColorType &&
!uirenderer::HardwareBitmapUploader::hasFP16Support()) {
- decodeColorType = kN32_SkColorType;
+ decodeColorType = kN32_SkColorType;
+ }
+ if (decodeColorType == kRGBA_1010102_SkColorType &&
+ !uirenderer::HardwareBitmapUploader::has1010102Support()) {
+ decodeColorType = kN32_SkColorType;
+ }
}
// Set up the pixel allocator
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 785a5dc995ab..15e529e169fc 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -107,7 +107,7 @@ private:
jint n = env->CallIntMethod(fJavaInputStream,
gInputStream_readMethodID, fJavaByteArray, 0, requested);
if (checkException(env)) {
- SkDebugf("---- read threw an exception\n");
+ ALOGD("---- read threw an exception\n");
return bytesRead;
}
@@ -119,7 +119,7 @@ private:
env->GetByteArrayRegion(fJavaByteArray, 0, n,
reinterpret_cast<jbyte*>(buffer));
if (checkException(env)) {
- SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
+ ALOGD("---- read:GetByteArrayRegion threw an exception\n");
return bytesRead;
}
@@ -136,7 +136,7 @@ private:
jlong skipped = env->CallLongMethod(fJavaInputStream,
gInputStream_skipMethodID, (jlong)size);
if (checkException(env)) {
- SkDebugf("------- skip threw an exception\n");
+ ALOGD("------- skip threw an exception\n");
return 0;
}
if (skipped < 0) {
@@ -236,7 +236,7 @@ public:
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
- SkDebugf("--- write:SetByteArrayElements threw an exception\n");
+ ALOGD("--- write:SetByteArrayElements threw an exception\n");
return false;
}
@@ -245,7 +245,7 @@ public:
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
- SkDebugf("------- write threw an exception\n");
+ ALOGD("------- write threw an exception\n");
return false;
}
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index f84a4bd09073..fef51b8d2f79 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -10,11 +10,13 @@
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkStream.h"
-#include "SkTemplates.h"
-#include "SkUtils.h"
#include "gif_lib.h"
+#include <log/log.h>
+
+#include <string.h>
+
#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
#define DGifCloseFile(a, b) DGifCloseFile(a)
#endif
@@ -217,8 +219,9 @@ static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, Gif
copyHeight = bmHeight - top;
}
+ size_t bytes = copyWidth * SkColorTypeBytesPerPixel(bm->colorType());
for (; copyHeight > 0; copyHeight--) {
- sk_memset32(dst, col, copyWidth);
+ memset(dst, col, bytes);
dst += bmWidth;
}
}
@@ -244,7 +247,7 @@ static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObjec
}
if (cmap == nullptr || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
- SkDEBUGFAIL("bad colortable setup");
+ ALOGD("bad colortable setup");
return;
}
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 77f46beb2100..33669ac0a34e 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -365,6 +365,8 @@ jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) {
return kRGB_565_LegacyBitmapConfig;
case kAlpha_8_SkColorType:
return kA8_LegacyBitmapConfig;
+ case kRGBA_1010102_SkColorType:
+ return kRGBA_1010102_LegacyBitmapConfig;
case kUnknown_SkColorType:
default:
break;
@@ -374,14 +376,10 @@ jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) {
SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) {
const uint8_t gConfig2ColorType[] = {
- kUnknown_SkColorType,
- kAlpha_8_SkColorType,
- kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
- kRGB_565_SkColorType,
- kARGB_4444_SkColorType,
- kN32_SkColorType,
- kRGBA_F16_SkColorType,
- kN32_SkColorType
+ kUnknown_SkColorType, kAlpha_8_SkColorType,
+ kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
+ kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType,
+ kRGBA_F16_SkColorType, kN32_SkColorType, kRGBA_1010102_SkColorType,
};
if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) {
@@ -399,15 +397,12 @@ AndroidBitmapFormat GraphicsJNI::getFormatFromConfig(JNIEnv* env, jobject jconfi
jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
const AndroidBitmapFormat config2BitmapFormat[] = {
- ANDROID_BITMAP_FORMAT_NONE,
- ANDROID_BITMAP_FORMAT_A_8,
- ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
- ANDROID_BITMAP_FORMAT_RGB_565,
- ANDROID_BITMAP_FORMAT_RGBA_4444,
- ANDROID_BITMAP_FORMAT_RGBA_8888,
- ANDROID_BITMAP_FORMAT_RGBA_F16,
- ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE
- };
+ ANDROID_BITMAP_FORMAT_NONE, ANDROID_BITMAP_FORMAT_A_8,
+ ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
+ ANDROID_BITMAP_FORMAT_RGB_565, ANDROID_BITMAP_FORMAT_RGBA_4444,
+ ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16,
+ ANDROID_BITMAP_FORMAT_NONE, // Congfig.HARDWARE
+ ANDROID_BITMAP_FORMAT_RGBA_1010102};
return config2BitmapFormat[javaConfigId];
}
@@ -430,6 +425,9 @@ jobject GraphicsJNI::getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format
case ANDROID_BITMAP_FORMAT_RGBA_F16:
configId = kRGBA_16F_LegacyBitmapConfig;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ configId = kRGBA_1010102_LegacyBitmapConfig;
+ break;
default:
break;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index ba407f2164de..085a905abaf8 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -34,16 +34,17 @@ public:
// This enum must keep these int values, to match the int values
// in the java Bitmap.Config enum.
enum LegacyBitmapConfig {
- kNo_LegacyBitmapConfig = 0,
- kA8_LegacyBitmapConfig = 1,
- kIndex8_LegacyBitmapConfig = 2,
- kRGB_565_LegacyBitmapConfig = 3,
- kARGB_4444_LegacyBitmapConfig = 4,
- kARGB_8888_LegacyBitmapConfig = 5,
- kRGBA_16F_LegacyBitmapConfig = 6,
- kHardware_LegacyBitmapConfig = 7,
-
- kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
+ kNo_LegacyBitmapConfig = 0,
+ kA8_LegacyBitmapConfig = 1,
+ kIndex8_LegacyBitmapConfig = 2,
+ kRGB_565_LegacyBitmapConfig = 3,
+ kARGB_4444_LegacyBitmapConfig = 4,
+ kARGB_8888_LegacyBitmapConfig = 5,
+ kRGBA_16F_LegacyBitmapConfig = 6,
+ kHardware_LegacyBitmapConfig = 7,
+ kRGBA_1010102_LegacyBitmapConfig = 8,
+
+ kLastEnum_LegacyBitmapConfig = kRGBA_1010102_LegacyBitmapConfig
};
static void setJavaVM(JavaVM* javaVM);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index 3ca457f1a6a7..08fc80fbdafd 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -64,7 +64,7 @@ public:
}
static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
- size_t chunkSize = env->GetArrayLength(obj);
+ size_t chunkSize = obj != NULL ? env->GetArrayLength(obj) : 0;
if (chunkSize < (int) (sizeof(Res_png_9patch))) {
jniThrowRuntimeException(env, "Array too small for chunk.");
return 0;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index bcec0fa8a1cc..f76863255153 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -541,26 +541,6 @@ namespace PaintGlue {
return result;
}
- // ------------------ @FastNative ---------------------------
-
- static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- ScopedUtfChars localesChars(env, locales);
- jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str());
- obj->setMinikinLocaleListId(minikinLocaleListId);
- return minikinLocaleListId;
- }
-
- static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- if (!settings) {
- paint->setFontFeatureSettings(std::string());
- } else {
- ScopedUtfChars settingsChars(env, settings);
- paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
- }
- }
-
static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
@@ -593,6 +573,67 @@ namespace PaintGlue {
return spacing;
}
+ static void doFontExtent(JNIEnv* env, jlong paintHandle, const jchar buf[], jint start,
+ jint count, jint bufSize, jboolean isRtl, jobject fmi) {
+ const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ const Typeface* typeface = paint->getAndroidTypeface();
+ minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ minikin::MinikinExtent extent =
+ MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
+
+ SkFontMetrics metrics;
+ getMetricsInternal(paintHandle, &metrics);
+
+ metrics.fAscent = extent.ascent;
+ metrics.fDescent = extent.descent;
+
+ // If top/bottom is narrower than ascent/descent, adjust top/bottom to ascent/descent.
+ metrics.fTop = std::min(metrics.fAscent, metrics.fTop);
+ metrics.fBottom = std::max(metrics.fDescent, metrics.fBottom);
+
+ GraphicsJNI::set_metrics_int(env, fmi, metrics);
+ }
+
+ static void getFontMetricsIntForText___C(JNIEnv* env, jclass, jlong paintHandle,
+ jcharArray text, jint start, jint count, jint ctxStart,
+ jint ctxCount, jboolean isRtl, jobject fmi) {
+ ScopedCharArrayRO textArray(env, text);
+
+ doFontExtent(env, paintHandle, textArray.get() + ctxStart, start - ctxStart, count,
+ ctxCount, isRtl, fmi);
+ }
+
+ static void getFontMetricsIntForText___String(JNIEnv* env, jclass, jlong paintHandle,
+ jstring text, jint start, jint count,
+ jint ctxStart, jint ctxCount, jboolean isRtl,
+ jobject fmi) {
+ ScopedStringChars textChars(env, text);
+
+ doFontExtent(env, paintHandle, textChars.get() + ctxStart, start - ctxStart, count,
+ ctxCount, isRtl, fmi);
+ }
+
+ // ------------------ @FastNative ---------------------------
+
+ static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ ScopedUtfChars localesChars(env, locales);
+ jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str());
+ obj->setMinikinLocaleListId(minikinLocaleListId);
+ return minikinLocaleListId;
+ }
+
+ static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle,
+ jstring settings) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (!settings) {
+ paint->setFontFeatureSettings(std::string());
+ } else {
+ ScopedUtfChars settingsChars(env, settings);
+ paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+ }
+ }
+
static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
SkFontMetrics metrics;
SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
@@ -663,8 +704,7 @@ namespace PaintGlue {
}
static void setFilterBitmap(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean filterBitmap) {
- reinterpret_cast<Paint*>(paintHandle)->setFilterQuality(
- filterBitmap ? kLow_SkFilterQuality : kNone_SkFilterQuality);
+ reinterpret_cast<Paint*>(paintHandle)->setFilterBitmap(filterBitmap);
}
static void setDither(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean dither) {
@@ -1016,6 +1056,11 @@ static const JNINativeMethod methods[] = {
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
+ {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+ (void*)PaintGlue::getFontMetricsIntForText___C},
+ {"nGetFontMetricsIntForText",
+ "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+ (void*)PaintGlue::getFontMetricsIntForText___String},
// --------------- @FastNative ----------------------
@@ -1094,6 +1139,7 @@ static const JNINativeMethod methods[] = {
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
};
+
int register_android_graphics_Paint(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
}
diff --git a/libs/hwui/jni/PaintFilter.cpp b/libs/hwui/jni/PaintFilter.cpp
index 86d4742b949e..6b5310107c00 100644
--- a/libs/hwui/jni/PaintFilter.cpp
+++ b/libs/hwui/jni/PaintFilter.cpp
@@ -29,10 +29,6 @@ public:
fClearFlags = static_cast<uint16_t>(clearFlags);
fSetFlags = static_cast<uint16_t>(setFlags);
}
- void filter(SkPaint* paint) override {
- uint32_t flags = Paint::GetSkPaintJavaFlags(*paint);
- Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags);
- }
void filterFullPaint(Paint* paint) override {
paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags);
}
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index a48d7f734e29..213f35a81b88 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -127,6 +127,32 @@ static jlong createShaderEffect(
return reinterpret_cast<jlong>(shaderFilter.release());
}
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+ va_end(args);
+ return ret;
+}
+
+static jlong createRuntimeShaderEffect(JNIEnv* env, jobject, jlong shaderBuilderHandle,
+ jstring inputShaderName) {
+ SkRuntimeShaderBuilder* builder =
+ reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilderHandle);
+ ScopedUtfChars name(env, inputShaderName);
+
+ if (builder->child(name.c_str()).fChild == nullptr) {
+ ThrowIAEFmt(env,
+ "unable to find a uniform with the name '%s' of the correct "
+ "type defined by the provided RuntimeShader",
+ name.c_str());
+ return 0;
+ }
+
+ sk_sp<SkImageFilter> filter = SkImageFilters::RuntimeShader(*builder, name.c_str(), nullptr);
+ return reinterpret_cast<jlong>(filter.release());
+}
+
static void RenderEffect_safeUnref(SkImageFilter* filter) {
SkSafeUnref(filter);
}
@@ -136,15 +162,16 @@ static jlong getRenderEffectFinalizer(JNIEnv*, jobject) {
}
static const JNINativeMethod gRenderEffectMethods[] = {
- {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
- {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
- {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
- {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
- {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
- {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
- {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
- {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect}
-};
+ {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
+ {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
+ {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
+ {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
+ {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
+ {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
+ {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
+ {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect},
+ {"nativeCreateRuntimeShaderEffect", "(JLjava/lang/String;)J",
+ (void*)createRuntimeShaderEffect}};
int register_android_graphics_RenderEffect(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 90184432e8a4..0bbd8a8cf97c 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -64,7 +64,8 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) {
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool filter) {
+ jint tileModeX, jint tileModeY, bool filter,
+ bool isDirectSampled) {
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
sk_sp<SkImage> image;
if (bitmapHandle) {
@@ -79,8 +80,12 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j
}
SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
SkMipmapMode::kNone);
- sk_sp<SkShader> shader = image->makeShader(
- (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ sk_sp<SkShader> shader;
+ if (isDirectSampled) {
+ shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ } else {
+ shader = image->makeShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ }
ThrowIAE_IfNull(env, shader.get());
if (matrix) {
@@ -256,11 +261,10 @@ static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeShaderBuilder_delete));
}
-static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr,
- jboolean isOpaque) {
+static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- sk_sp<SkShader> shader = builder->makeShader(matrix, isOpaque == JNI_TRUE);
+ sk_sp<SkShader> shader = builder->makeShader(matrix);
ThrowIAE_IfNull(env, shader);
return reinterpret_cast<jlong>(shader.release());
}
@@ -273,21 +277,99 @@ static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
return ret;
}
-static void RuntimeShader_updateUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
- jstring jUniformName, jfloatArray jvalues) {
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+ switch (type) {
+ case SkRuntimeEffect::Uniform::Type::kFloat:
+ case SkRuntimeEffect::Uniform::Type::kFloat2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4:
+ case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+ return false;
+ case SkRuntimeEffect::Uniform::Type::kInt:
+ case SkRuntimeEffect::Uniform::Type::kInt2:
+ case SkRuntimeEffect::Uniform::Type::kInt3:
+ case SkRuntimeEffect::Uniform::Type::kInt4:
+ return true;
+ }
+}
+
+static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
+ const char* uniformName, const float values[], int count,
+ bool isColor) {
+ SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+ if (isColor) {
+ jniThrowExceptionFmt(
+ env, "java/lang/IllegalArgumentException",
+ "attempting to set a color uniform using the non-color specific APIs: %s %x",
+ uniformName, uniform.fVar->flags);
+ } else {
+ ThrowIAEFmt(env,
+ "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+ uniformName);
+ }
+ } else if (isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<float>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jfloat value1, jfloat value2,
+ jfloat value3, jfloat value4, jint count) {
+ SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ UpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jfloatArray jvalues,
+ jboolean isColor) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
ScopedUtfChars name(env, jUniformName);
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+ UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
+}
- SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(name.c_str());
+static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
+ const int values[], int count) {
+ SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", name.c_str());
- } else if (!uniform.set<float>(autoValues.ptr(), autoValues.length())) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (!isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<int>(values, count)) {
ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * autoValues.length());
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
}
}
+static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jint value1, jint value2,
+ jint value3, jint value4, jint count) {
+ SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ UpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void RuntimeShader_updateIntArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jintArray jvalues) {
+ SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ AutoJavaIntArray autoValues(env, jvalues, 0);
+ UpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jlong shaderHandle) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
@@ -295,7 +377,7 @@ static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder
SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
SkRuntimeShaderBuilder::BuilderChild child = builder->child(name.c_str());
- if (child.fIndex == -1) {
+ if (child.fChild == nullptr) {
ThrowIAEFmt(env, "unable to find shader named %s", name.c_str());
return;
}
@@ -315,7 +397,7 @@ static const JNINativeMethod gShaderMethods[] = {
};
static const JNINativeMethod gBitmapShaderMethods[] = {
- { "nativeCreate", "(JJIIZ)J", (void*)BitmapShader_constructor },
+ {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
};
static const JNINativeMethod gLinearGradientMethods[] = {
@@ -336,9 +418,16 @@ static const JNINativeMethod gComposeShaderMethods[] = {
static const JNINativeMethod gRuntimeShaderMethods[] = {
{"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer},
- {"nativeCreateShader", "(JJZ)J", (void*)RuntimeShader_create},
+ {"nativeCreateShader", "(JJ)J", (void*)RuntimeShader_create},
{"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder},
- {"nativeUpdateUniforms", "(JLjava/lang/String;[F)V", (void*)RuntimeShader_updateUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+ (void*)RuntimeShader_updateFloatArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+ (void*)RuntimeShader_updateFloatUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+ (void*)RuntimeShader_updateIntArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+ (void*)RuntimeShader_updateIntUniforms},
{"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
};
diff --git a/libs/hwui/jni/Utils.cpp b/libs/hwui/jni/Utils.cpp
index ac2f5b77d23a..106c6db57e18 100644
--- a/libs/hwui/jni/Utils.cpp
+++ b/libs/hwui/jni/Utils.cpp
@@ -18,6 +18,7 @@
#include "SkUtils.h"
#include "SkData.h"
+#include <inttypes.h>
#include <log/log.h>
using namespace android;
@@ -30,7 +31,7 @@ AssetStreamAdaptor::AssetStreamAdaptor(Asset* asset)
bool AssetStreamAdaptor::rewind() {
off64_t pos = fAsset->seek(0, SEEK_SET);
if (pos == (off64_t)-1) {
- SkDebugf("----- fAsset->seek(rewind) failed\n");
+ ALOGD("----- fAsset->seek(rewind) failed\n");
return false;
}
return true;
@@ -58,7 +59,7 @@ bool AssetStreamAdaptor::hasPosition() const {
size_t AssetStreamAdaptor::getPosition() const {
const off64_t offset = fAsset->seek(0, SEEK_CUR);
if (offset == -1) {
- SkDebugf("---- fAsset->seek(0, SEEK_CUR) failed\n");
+ ALOGD("---- fAsset->seek(0, SEEK_CUR) failed\n");
return 0;
}
@@ -67,7 +68,7 @@ size_t AssetStreamAdaptor::getPosition() const {
bool AssetStreamAdaptor::seek(size_t position) {
if (fAsset->seek(position, SEEK_SET) == -1) {
- SkDebugf("---- fAsset->seek(0, SEEK_SET) failed\n");
+ ALOGD("---- fAsset->seek(0, SEEK_SET) failed\n");
return false;
}
@@ -76,7 +77,7 @@ bool AssetStreamAdaptor::seek(size_t position) {
bool AssetStreamAdaptor::move(long offset) {
if (fAsset->seek(offset, SEEK_CUR) == -1) {
- SkDebugf("---- fAsset->seek(%i, SEEK_CUR) failed\n", offset);
+ ALOGD("---- fAsset->seek(%li, SEEK_CUR) failed\n", offset);
return false;
}
@@ -95,12 +96,12 @@ size_t AssetStreamAdaptor::read(void* buffer, size_t size) {
off64_t oldOffset = fAsset->seek(0, SEEK_CUR);
if (-1 == oldOffset) {
- SkDebugf("---- fAsset->seek(oldOffset) failed\n");
+ ALOGD("---- fAsset->seek(oldOffset) failed\n");
return 0;
}
off64_t newOffset = fAsset->seek(size, SEEK_CUR);
if (-1 == newOffset) {
- SkDebugf("---- fAsset->seek(%d) failed\n", size);
+ ALOGD("---- fAsset->seek(%zu) failed\n", size);
return 0;
}
amount = newOffset - oldOffset;
@@ -121,20 +122,20 @@ sk_sp<SkData> android::CopyAssetToData(Asset* asset) {
const off64_t seekReturnVal = asset->seek(0, SEEK_SET);
if ((off64_t)-1 == seekReturnVal) {
- SkDebugf("---- copyAsset: asset rewind failed\n");
+ ALOGD("---- copyAsset: asset rewind failed\n");
return NULL;
}
const off64_t size = asset->getLength();
if (size <= 0) {
- SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
+ ALOGD("---- copyAsset: asset->getLength() returned %" PRId64 "\n", size);
return NULL;
}
sk_sp<SkData> data(SkData::MakeUninitialized(size));
const off64_t len = asset->read(data->writable_data(), size);
if (len != size) {
- SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
+ ALOGD("---- copyAsset: asset->read(%" PRId64 ") returned %" PRId64 "\n", size, len);
return NULL;
}
@@ -143,7 +144,7 @@ sk_sp<SkData> android::CopyAssetToData(Asset* asset) {
jobject android::nullObjectReturn(const char msg[]) {
if (msg) {
- SkDebugf("--- %s\n", msg);
+ ALOGD("--- %s\n", msg);
}
return NULL;
}
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 689cf0bea741..77f42ae70268 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -85,7 +85,7 @@ Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) :
void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo,
uint8_t* yuv, int* offsets) {
- SkDebugf("onFlyCompress");
+ ALOGD("onFlyCompress");
JSAMPROW y[16];
JSAMPROW cb[8];
JSAMPROW cr[8];
@@ -161,7 +161,7 @@ Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) :
void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo,
uint8_t* yuv, int* offsets) {
- SkDebugf("onFlyCompress_422");
+ ALOGD("onFlyCompress_422");
JSAMPROW y[16];
JSAMPROW cb[16];
JSAMPROW cr[16];
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index a611f7ce2d14..0ef80ee10708 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -188,39 +188,57 @@ static jboolean quickRejectPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jl
return result ? JNI_TRUE : JNI_FALSE;
}
-// SkRegion::Op and SkClipOp are numerically identical, so we can freely cast
-// from one to the other (though SkClipOp is destined to become a strict subset)
+// SkClipOp is a strict subset of SkRegion::Op and is castable back and forth for their
+// shared operations (intersect and difference).
static_assert(SkRegion::kDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kDifference), "");
static_assert(SkRegion::kIntersect_Op == static_cast<SkRegion::Op>(SkClipOp::kIntersect), "");
-static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(SkClipOp::kUnion_deprecated), "");
-static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(SkClipOp::kXOR_deprecated), "");
-static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kReverseDifference_deprecated), "");
-static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(SkClipOp::kReplace_deprecated), "");
-
-static SkClipOp opHandleToClipOp(jint opHandle) {
- // The opHandle is defined in Canvas.java to be Region::Op
- SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle);
-
- // In the future, when we no longer support the wide range of ops (e.g. Union, Xor)
- // this function can perform a range check and throw an unsupported-exception.
- // e.g. if (rgnOp != kIntersect && rgnOp != kDifference) throw...
-
- // Skia now takes a different type, SkClipOp, as the parameter to clipping calls
- // This type is binary compatible with SkRegion::Op, so a static_cast<> is safe.
- return static_cast<SkClipOp>(rgnOp);
-}
static jboolean clipRect(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint opHandle) {
- bool nonEmptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b,
- opHandleToClipOp(opHandle));
+ // The opHandle is defined in Canvas.java to be Region::Op
+ SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle);
+ bool nonEmptyClip;
+ switch (rgnOp) {
+ case SkRegion::Op::kIntersect_Op:
+ case SkRegion::Op::kDifference_Op:
+ // Intersect and difference are supported clip operations
+ nonEmptyClip =
+ get_canvas(canvasHandle)->clipRect(l, t, r, b, static_cast<SkClipOp>(rgnOp));
+ break;
+ case SkRegion::Op::kReplace_Op:
+ // Replace is emulated to support legacy apps older than P
+ nonEmptyClip = get_canvas(canvasHandle)->replaceClipRect_deprecated(l, t, r, b);
+ break;
+ default:
+ // All other operations would expand the clip and are no longer supported,
+ // so log and skip (to avoid breaking legacy apps).
+ ALOGW("Ignoring unsupported clip operation %d", opHandle);
+ SkRect clipBounds; // ignored
+ nonEmptyClip = get_canvas(canvasHandle)->getClipBounds(&clipBounds);
+ break;
+ }
return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
static jboolean clipPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong pathHandle,
jint opHandle) {
+ SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle);
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- bool nonEmptyClip = get_canvas(canvasHandle)->clipPath(path, opHandleToClipOp(opHandle));
+ bool nonEmptyClip;
+ switch (rgnOp) {
+ case SkRegion::Op::kIntersect_Op:
+ case SkRegion::Op::kDifference_Op:
+ nonEmptyClip = get_canvas(canvasHandle)->clipPath(path, static_cast<SkClipOp>(rgnOp));
+ break;
+ case SkRegion::Op::kReplace_Op:
+ nonEmptyClip = get_canvas(canvasHandle)->replaceClipPath_deprecated(path);
+ break;
+ default:
+ ALOGW("Ignoring unsupported clip operation %d", opHandle);
+ SkRect clipBounds; // ignored
+ nonEmptyClip = get_canvas(canvasHandle)->getClipBounds(&clipBounds);
+ break;
+ }
return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
@@ -233,7 +251,7 @@ static void drawColorLong(JNIEnv* env, jobject, jlong canvasHandle, jlong colorS
jlong colorLong, jint modeHandle) {
SkColor4f color = GraphicsJNI::convertColorLong(colorLong);
sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
- SkPaint p;
+ Paint p;
p.setColor4f(color, cs.get());
SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
@@ -421,7 +439,7 @@ static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmap
if (paint) {
filteredPaint = *paint;
}
- filteredPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.setFilterBitmap(true);
canvas->drawNinePatch(bitmap, *chunk, 0, 0, (right-left)/scale, (bottom-top)/scale,
&filteredPaint);
@@ -443,7 +461,7 @@ static void drawBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHan
if (paint) {
filteredPaint = *paint;
}
- filteredPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(bitmap, left, top, &filteredPaint);
} else {
canvas->drawBitmap(bitmap, left, top, paint);
@@ -458,7 +476,7 @@ static void drawBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHan
if (paint) {
filteredPaint = *paint;
}
- filteredPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(bitmap, 0, 0, &filteredPaint);
canvas->restore();
@@ -486,7 +504,7 @@ static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitma
if (paint) {
filteredPaint = *paint;
}
- filteredPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
dstLeft, dstTop, dstRight, dstBottom, &filteredPaint);
} else {
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index b5536ad4830d..c48448dffdd2 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -259,7 +259,8 @@ static void android_view_ThreadedRenderer_setIsHighEndGfx(JNIEnv* env, jobject c
}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
+ jlong proxyPtr, jlongArray frameInfo,
+ jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %zu", frameInfoSize,
UI_THREAD_FRAME_INFO_SIZE);
@@ -379,6 +380,13 @@ static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject c
proxy->dumpProfileInfo(fd, dumpFlags);
}
+static void android_view_ThreadedRenderer_dumpGlobalProfileInfo(JNIEnv* env, jobject clazz,
+ jobject javaFileDescriptor,
+ jint dumpFlags) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
+ RenderProxy::dumpGraphicsMemory(fd, true, dumpFlags & DumpFlags::Reset);
+}
+
static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -406,6 +414,12 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env,
proxy->setContentDrawBounds(left, top, right, bottom);
}
+static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobject clazz,
+ jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->forceDrawNextFrame();
+}
+
class JGlobalRefHolder {
public:
JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
@@ -426,28 +440,6 @@ private:
jobject mObject;
};
-class JWeakGlobalRefHolder {
-public:
- JWeakGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm) {
- mWeakRef = getenv(vm)->NewWeakGlobalRef(object);
- }
-
- virtual ~JWeakGlobalRefHolder() {
- if (mWeakRef != nullptr) getenv(mVm)->DeleteWeakGlobalRef(mWeakRef);
- mWeakRef = nullptr;
- }
-
- jobject ref() { return mWeakRef; }
- JavaVM* vm() { return mVm; }
-
-private:
- JWeakGlobalRefHolder(const JWeakGlobalRefHolder&) = delete;
- void operator=(const JWeakGlobalRefHolder&) = delete;
-
- JavaVM* mVm;
- jobject mWeakRef;
-};
-
using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>;
struct PictureCaptureState {
@@ -581,20 +573,16 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback(
} else {
JavaVM* vm = nullptr;
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
- auto globalCallbackRef =
- std::make_shared<JWeakGlobalRefHolder>(vm, aSurfaceTransactionCallback);
+ auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(
+ vm, env->NewGlobalRef(aSurfaceTransactionCallback));
proxy->setASurfaceTransactionCallback(
[globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool {
JNIEnv* env = getenv(globalCallbackRef->vm());
- jobject localref = env->NewLocalRef(globalCallbackRef->ref());
- if (CC_UNLIKELY(!localref)) {
- return false;
- }
jboolean ret = env->CallBooleanMethod(
- localref, gASurfaceTransactionCallback.onMergeTransaction,
+ globalCallbackRef->object(),
+ gASurfaceTransactionCallback.onMergeTransaction,
static_cast<jlong>(transObj), static_cast<jlong>(scObj),
static_cast<jlong>(frameNr));
- env->DeleteLocalRef(localref);
return ret;
});
}
@@ -609,15 +597,11 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall
JavaVM* vm = nullptr;
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
auto globalCallbackRef =
- std::make_shared<JWeakGlobalRefHolder>(vm, callback);
+ std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() {
JNIEnv* env = getenv(globalCallbackRef->vm());
- jobject localref = env->NewLocalRef(globalCallbackRef->ref());
- if (CC_UNLIKELY(!localref)) {
- return;
- }
- env->CallVoidMethod(localref, gPrepareSurfaceControlForWebviewCallback.prepare);
- env->DeleteLocalRef(localref);
+ env->CallVoidMethod(globalCallbackRef->object(),
+ gPrepareSurfaceControlForWebviewCallback.prepare);
});
}
}
@@ -632,10 +616,19 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
env->NewGlobalRef(frameCallback));
- proxy->setFrameCallback([globalCallbackRef](int64_t frameNr) {
+ proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
+ int64_t frameNr) -> std::function<void(bool)> {
JNIEnv* env = getenv(globalCallbackRef->vm());
- env->CallVoidMethod(globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
- static_cast<jlong>(frameNr));
+ ScopedLocalRef<jobject> frameCommitCallback(
+ env, env->CallObjectMethod(
+ globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
+ static_cast<jint>(syncResult), static_cast<jlong>(frameNr)));
+ if (frameCommitCallback == nullptr) {
+ return nullptr;
+ }
+ sp<FrameCommitWrapper> wrapper =
+ sp<FrameCommitWrapper>::make(env, frameCommitCallback.get());
+ return [wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); };
});
}
}
@@ -646,7 +639,7 @@ static void android_view_ThreadedRenderer_setFrameCommitCallback(JNIEnv* env, jo
if (!callback) {
proxy->setFrameCommitCallback(nullptr);
} else {
- sp<FrameCommitWrapper> wrapper = new FrameCommitWrapper{env, callback};
+ sp<FrameCommitWrapper> wrapper = sp<FrameCommitWrapper>::make(env, callback);
proxy->setFrameCommitCallback(
[wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); });
}
@@ -784,11 +777,6 @@ static void android_view_ThreadedRenderer_setHighContrastText(JNIEnv*, jclass, j
Properties::enableHighContrastText = enable;
}
-static void android_view_ThreadedRenderer_hackySetRTAnimationsEnabled(JNIEnv*, jclass,
- jboolean enable) {
- Properties::enableRTAnimations = enable;
-}
-
static void android_view_ThreadedRenderer_setDebuggingEnabled(JNIEnv*, jclass, jboolean enable) {
Properties::debuggingEnabled = enable;
}
@@ -818,6 +806,11 @@ static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
RenderProxy::preload();
}
+static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jobject clazz,
+ jboolean enabled) {
+ RenderProxy::setRtAnimationsEnabled(enabled);
+}
+
// Plumbs the display density down to DeviceInfo.
static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
// Convert from dpi to density-independent pixels.
@@ -838,6 +831,14 @@ static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint
DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
}
+static void android_view_ThreadedRenderer_setDrawingEnabled(JNIEnv*, jclass, jboolean enabled) {
+ Properties::setDrawingEnabled(enabled);
+}
+
+static jboolean android_view_ThreadedRenderer_isDrawingEnabled(JNIEnv*, jclass) {
+ return Properties::isDrawingEnabled();
+}
+
// ----------------------------------------------------------------------------
// HardwareRendererObserver
// ----------------------------------------------------------------------------
@@ -932,6 +933,8 @@ static const JNINativeMethod gMethods[] = {
{"nNotifyFramePending", "(J)V", (void*)android_view_ThreadedRenderer_notifyFramePending},
{"nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V",
(void*)android_view_ThreadedRenderer_dumpProfileInfo},
+ {"nDumpGlobalProfileInfo", "(Ljava/io/FileDescriptor;I)V",
+ (void*)android_view_ThreadedRenderer_dumpGlobalProfileInfo},
{"setupShadersDiskCache", "(Ljava/lang/String;Ljava/lang/String;)V",
(void*)android_view_ThreadedRenderer_setupShadersDiskCache},
{"nAddRenderNode", "(JJZ)V", (void*)android_view_ThreadedRenderer_addRenderNode},
@@ -939,6 +942,7 @@ static const JNINativeMethod gMethods[] = {
{"nDrawRenderNode", "(JJ)V", (void*)android_view_ThreadedRendererd_drawRenderNode},
{"nSetContentDrawBounds", "(JIIII)V",
(void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ {"nForceDrawNextFrame", "(J)V", (void*)android_view_ThreadedRenderer_forceDrawNextFrame},
{"nSetPictureCaptureCallback",
"(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
(void*)android_view_ThreadedRenderer_setPictureCapturedCallbackJNI},
@@ -963,8 +967,6 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode},
{"disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync},
{"nSetHighContrastText", "(Z)V", (void*)android_view_ThreadedRenderer_setHighContrastText},
- {"nHackySetRTAnimationsEnabled", "(Z)V",
- (void*)android_view_ThreadedRenderer_hackySetRTAnimationsEnabled},
{"nSetDebuggingEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setDebuggingEnabled},
{"nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess},
{"nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority},
@@ -976,6 +978,10 @@ static const JNINativeMethod gMethods[] = {
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
{"isWebViewOverlaysEnabled", "()Z",
(void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
+ {"nSetDrawingEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setDrawingEnabled},
+ {"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled},
+ {"nSetRtAnimationsEnabled", "(Z)V",
+ (void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
};
static JavaVM* mJvm = nullptr;
@@ -1014,8 +1020,9 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
- gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
- "onFrameDraw", "(J)V");
+ gFrameDrawingCallback.onFrameDraw =
+ GetMethodIDOrDie(env, frameCallbackClass, "onFrameDraw",
+ "(IJ)Landroid/graphics/HardwareRenderer$FrameCommitCallback;");
jclass frameCommitClass =
FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameCommitCallback");
diff --git a/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp b/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp
index e5d5e75d0f3b..6cae5ffa397f 100644
--- a/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp
@@ -24,6 +24,7 @@
namespace android {
struct {
+ jclass clazz;
jmethodID callback;
} gHardwareRendererObserverClassInfo;
@@ -38,14 +39,13 @@ static JNIEnv* getenv(JavaVM* vm) {
HardwareRendererObserver::HardwareRendererObserver(JavaVM* vm, jobject observer,
bool waitForPresentTime)
: uirenderer::FrameMetricsObserver(waitForPresentTime), mVm(vm) {
- mObserverWeak = getenv(mVm)->NewWeakGlobalRef(observer);
- LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
- "unable to create frame stats observer reference");
+ mObserver = getenv(mVm)->NewGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserver == nullptr, "unable to create frame stats observer reference");
}
HardwareRendererObserver::~HardwareRendererObserver() {
JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mObserverWeak);
+ env->DeleteGlobalRef(mObserver);
}
bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount) {
@@ -66,6 +66,8 @@ bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, in
}
void HardwareRendererObserver::notify(const int64_t* stats) {
+ if (!mKeepListening) return;
+
FrameMetricsNotification& elem = mRingBuffer[mNextFree];
if (!elem.hasData.load()) {
@@ -77,18 +79,17 @@ void HardwareRendererObserver::notify(const int64_t* stats) {
elem.hasData = true;
JNIEnv* env = getenv(mVm);
- jobject target = env->NewLocalRef(mObserverWeak);
- if (target != nullptr) {
- env->CallVoidMethod(target, gHardwareRendererObserverClassInfo.callback);
- env->DeleteLocalRef(target);
- }
+ mKeepListening = env->CallStaticBooleanMethod(gHardwareRendererObserverClassInfo.clazz,
+ gHardwareRendererObserverClassInfo.callback,
+ mObserver);
} else {
mDroppedReports++;
}
}
static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* env,
- jobject observerObj,
+ jobject /*clazz*/,
+ jobject weakRefThis,
jboolean waitForPresentTime) {
JavaVM* vm = nullptr;
if (env->GetJavaVM(&vm) != JNI_OK) {
@@ -97,7 +98,7 @@ static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* en
}
HardwareRendererObserver* observer =
- new HardwareRendererObserver(vm, observerObj, waitForPresentTime);
+ new HardwareRendererObserver(vm, weakRefThis, waitForPresentTime);
return reinterpret_cast<jlong>(observer);
}
@@ -114,7 +115,7 @@ static jint android_graphics_HardwareRendererObserver_getNextBuffer(JNIEnv* env,
}
static const std::array gMethods = {
- MAKE_JNI_NATIVE_METHOD("nCreateObserver", "(Z)J",
+ MAKE_JNI_NATIVE_METHOD("nCreateObserver", "(Ljava/lang/ref/WeakReference;Z)J",
android_graphics_HardwareRendererObserver_createObserver),
MAKE_JNI_NATIVE_METHOD("nGetNextBuffer", "(J[J)I",
android_graphics_HardwareRendererObserver_getNextBuffer),
@@ -123,8 +124,10 @@ static const std::array gMethods = {
int register_android_graphics_HardwareRendererObserver(JNIEnv* env) {
jclass observerClass = FindClassOrDie(env, "android/graphics/HardwareRendererObserver");
- gHardwareRendererObserverClassInfo.callback = GetMethodIDOrDie(env, observerClass,
- "notifyDataAvailable", "()V");
+ gHardwareRendererObserverClassInfo.clazz =
+ reinterpret_cast<jclass>(env->NewGlobalRef(observerClass));
+ gHardwareRendererObserverClassInfo.callback = GetStaticMethodIDOrDie(
+ env, observerClass, "invokeDataAvailable", "(Ljava/lang/ref/WeakReference;)Z");
return RegisterMethodsOrDie(env, "android/graphics/HardwareRendererObserver",
gMethods.data(), gMethods.size());
diff --git a/libs/hwui/jni/android_graphics_HardwareRendererObserver.h b/libs/hwui/jni/android_graphics_HardwareRendererObserver.h
index d3076140541b..5ee3e1669502 100644
--- a/libs/hwui/jni/android_graphics_HardwareRendererObserver.h
+++ b/libs/hwui/jni/android_graphics_HardwareRendererObserver.h
@@ -63,7 +63,8 @@ private:
};
JavaVM* const mVm;
- jweak mObserverWeak;
+ jobject mObserver;
+ bool mKeepListening = true;
int mNextFree = 0;
int mNextInQueue = 0;
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index e1da1690518a..944393c63ad6 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -547,9 +547,12 @@ static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz,
// SurfaceView position callback
// ----------------------------------------------------------------------------
-jmethodID gPositionListener_PositionChangedMethod;
-jmethodID gPositionListener_ApplyStretchMethod;
-jmethodID gPositionListener_PositionLostMethod;
+struct {
+ jclass clazz;
+ jmethodID callPositionChanged;
+ jmethodID callApplyStretch;
+ jmethodID callPositionLost;
+} gPositionListener;
static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
jlong renderNodePtr, jobject listener) {
@@ -557,16 +560,16 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
public:
PositionListenerTrampoline(JNIEnv* env, jobject listener) {
env->GetJavaVM(&mVm);
- mWeakRef = env->NewWeakGlobalRef(listener);
+ mListener = env->NewGlobalRef(listener);
}
virtual ~PositionListenerTrampoline() {
- jnienv()->DeleteWeakGlobalRef(mWeakRef);
- mWeakRef = nullptr;
+ jnienv()->DeleteGlobalRef(mListener);
+ mListener = nullptr;
}
virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
- if (CC_UNLIKELY(!mWeakRef || !info.updateWindowPositions)) return;
+ if (CC_UNLIKELY(!mListener || !info.updateWindowPositions)) return;
Matrix4 transform;
info.damageAccumulator->computeCurrentTransform(&transform);
@@ -609,7 +612,7 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
}
virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
- if (CC_UNLIKELY(!mWeakRef || (info && !info->updateWindowPositions))) return;
+ if (CC_UNLIKELY(!mListener || (info && !info->updateWindowPositions))) return;
if (mPreviousPosition.isEmpty()) {
return;
@@ -618,18 +621,16 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
ATRACE_NAME("SurfaceView position lost");
JNIEnv* env = jnienv();
- jobject localref = env->NewLocalRef(mWeakRef);
- if (CC_UNLIKELY(!localref)) {
- env->DeleteWeakGlobalRef(mWeakRef);
- mWeakRef = nullptr;
- return;
- }
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
// TODO: Remember why this is synchronous and then make a comment
- env->CallVoidMethod(localref, gPositionListener_PositionLostMethod,
+ jboolean keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionLost, mListener,
info ? info->canvasContext.getFrameNumber() : 0);
+ if (!keepListening) {
+ env->DeleteGlobalRef(mListener);
+ mListener = nullptr;
+ }
#endif
- env->DeleteLocalRef(localref);
}
private:
@@ -684,28 +685,20 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
StretchEffectBehavior::Shader) {
JNIEnv* env = jnienv();
- jobject localref = env->NewLocalRef(mWeakRef);
- if (CC_UNLIKELY(!localref)) {
- env->DeleteWeakGlobalRef(mWeakRef);
- mWeakRef = nullptr;
- return;
- }
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
SkVector stretchDirection = effect->getStretchDirection();
- env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
- info.canvasContext.getFrameNumber(),
- result.width,
- result.height,
- stretchDirection.fX,
- stretchDirection.fY,
- effect->maxStretchAmountX,
- effect->maxStretchAmountY,
- childRelativeBounds.left(),
- childRelativeBounds.top(),
- childRelativeBounds.right(),
- childRelativeBounds.bottom());
+ jboolean keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callApplyStretch, mListener,
+ info.canvasContext.getFrameNumber(), result.width, result.height,
+ stretchDirection.fX, stretchDirection.fY, effect->maxStretchAmountX,
+ effect->maxStretchAmountY, childRelativeBounds.left(),
+ childRelativeBounds.top(), childRelativeBounds.right(),
+ childRelativeBounds.bottom());
+ if (!keepListening) {
+ env->DeleteGlobalRef(mListener);
+ mListener = nullptr;
+ }
#endif
- env->DeleteLocalRef(localref);
}
}
@@ -714,14 +707,12 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
ATRACE_NAME("Update SurfaceView position");
JNIEnv* env = jnienv();
- jobject localref = env->NewLocalRef(mWeakRef);
- if (CC_UNLIKELY(!localref)) {
- env->DeleteWeakGlobalRef(mWeakRef);
- mWeakRef = nullptr;
- } else {
- env->CallVoidMethod(localref, gPositionListener_PositionChangedMethod,
- frameNumber, left, top, right, bottom);
- env->DeleteLocalRef(localref);
+ 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
@@ -729,7 +720,7 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
}
JavaVM* mVm;
- jobject mWeakRef;
+ jobject mListener;
uirenderer::Rect mPreviousPosition;
};
@@ -754,7 +745,7 @@ static const JNINativeMethod gMethods[] = {
{"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize},
{"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator},
{"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators},
- {"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V",
+ {"nRequestPositionUpdates", "(JLjava/lang/ref/WeakReference;)V",
(void*)android_view_RenderNode_requestPositionUpdates},
// ----------------------------------------------------------------------------
@@ -852,12 +843,13 @@ static const JNINativeMethod gMethods[] = {
int register_android_view_RenderNode(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/graphics/RenderNode$PositionUpdateListener");
- gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
- "positionChanged", "(JIIII)V");
- gPositionListener_ApplyStretchMethod =
- GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFFFFF)V");
- gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
- "positionLost", "(J)V");
+ gPositionListener.clazz = MakeGlobalRefOrDie(env, clazz);
+ gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
+ env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
+ gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
+ env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
+ gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
+ env, clazz, "callPositionLost", "(Ljava/lang/ref/WeakReference;J)Z");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 7793746ee285..c13c800651ef 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -65,11 +65,13 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) {
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint start, jint end, jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
+ jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
- toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), isRtl);
+ toBuilder(builderPtr)
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
}
// Regular JNI
@@ -80,15 +82,17 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong
}
// Regular JNI
-static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
- jlong hintPtr, jcharArray javaText, jboolean computeHyphenation,
- jboolean computeLayout) {
+static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
+ jcharArray javaText, jboolean computeHyphenation,
+ jboolean computeLayout, jboolean fastHyphenationMode) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
- return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout,
- toMeasuredParagraph(hintPtr)).release());
+ return toJLong(toBuilder(builderPtr)
+ ->build(textBuffer, computeHyphenation, computeLayout,
+ fastHyphenationMode, toMeasuredParagraph(hintPtr))
+ .release());
}
// Regular JNI
@@ -130,6 +134,21 @@ static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jin
GraphicsJNI::irect_to_jrect(ir, env, bounds);
}
+// Regular JNI
+static jlong nGetExtent(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start,
+ jint end) {
+ ScopedCharArrayRO text(env, javaText);
+ const minikin::U16StringPiece textBuffer(text.get(), text.size());
+ const minikin::Range range(start, end);
+
+ minikin::MinikinExtent extent = toMeasuredParagraph(ptr)->getExtent(textBuffer, range);
+
+ int32_t ascent = SkScalarRoundToInt(extent.ascent);
+ int32_t descent = SkScalarRoundToInt(extent.descent);
+
+ return (((jlong)(ascent)) << 32) | ((jlong)descent);
+}
+
// CriticalNative
static jlong nGetReleaseFunc(CRITICAL_JNI_PARAMS) {
return toJLong(&releaseMeasuredParagraph);
@@ -140,21 +159,22 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
}
static const JNINativeMethod gMTBuilderMethods[] = {
- // MeasuredParagraphBuilder native functions.
- {"nInitBuilder", "()J", (void*) nInitBuilder},
- {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
- {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText},
- {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
+ // MeasuredParagraphBuilder native functions.
+ {"nInitBuilder", "()J", (void*)nInitBuilder},
+ {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+ {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
+ {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+ {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
};
static const JNINativeMethod gMTMethods[] = {
- // MeasuredParagraph native functions.
- {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives
- {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI
- {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
- {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native
- {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native
+ // MeasuredParagraph native functions.
+ {"nGetWidth", "(JII)F", (void*)nGetWidth}, // Critical Natives
+ {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*)nGetBounds}, // Regular JNI
+ {"nGetExtent", "(J[CII)J", (void*)nGetExtent}, // Regular JNI
+ {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc}, // Critical Natives
+ {"nGetMemoryUsage", "(J)I", (void*)nGetMemoryUsage}, // Critical Native
+ {"nGetCharWidthAt", "(JI)F", (void*)nGetCharWidthAt}, // Critical Native
};
int register_android_graphics_text_MeasuredText(JNIEnv* env) {
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index a6fb95832c03..8e4dd53069f4 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -160,7 +160,6 @@ static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) {
}
static const JNINativeMethod gMethods[] = {
- // Fast Natives
{"nativeShapeTextRun", "("
"[C" // text
"I" // start
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index 77b8a44d85a1..087c006a8680 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -1,4 +1,4 @@
-LIBHWUI {
+LIBHWUI { # platform-only /* HWUI isn't current a module, so all of these are still platform-only */
global:
/* listing of all C APIs to be exposed by libhwui to consumers outside of the module */
ABitmap_getInfoFromJava;
@@ -39,7 +39,6 @@ LIBHWUI {
ARegionIterator_next;
ARegionIterator_getRect;
ARegionIterator_getTotalBounds;
- ARenderThread_dumpGraphicsMemory;
local:
*;
};
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index d173782fd880..9cf93e66cfbe 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -110,7 +110,7 @@ public:
const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
setUniform2f(effectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
- params.paint->value.setShader(effectBuilder.makeShader(nullptr, false));
+ params.paint->value.setShader(effectBuilder.makeShader());
canvas->drawCircle(params.x->value, params.y->value, params.radius->value,
params.paint->value);
}
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 3580bed45a1f..3f89c0712407 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -52,6 +52,8 @@ protected:
mOutput << mIdent << "clipRegion" << std::endl;
}
+ void onResetClip() override { mOutput << mIdent << "resetClip" << std::endl; }
+
void onDrawPaint(const SkPaint&) override { mOutput << mIdent << "drawPaint" << std::endl; }
void onDrawPath(const SkPath&, const SkPaint&) override {
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 471a7f7af3b1..2fba13c3cfea 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -15,12 +15,20 @@
*/
#include "LayerDrawable.h"
+
+#include <shaders/shaders.h>
+#include <utils/Color.h>
#include <utils/MathUtils.h>
+#include "DeviceInfo.h"
#include "GrBackendSurface.h"
#include "SkColorFilter.h"
+#include "SkRuntimeEffect.h"
#include "SkSurface.h"
#include "gl/GrGLTypes.h"
+#include "math/mat4.h"
+#include "system/graphics-base-v1.0.h"
+#include "system/window.h"
namespace android {
namespace uirenderer {
@@ -29,7 +37,8 @@ namespace skiapipeline {
void LayerDrawable::onDraw(SkCanvas* canvas) {
Layer* layer = mLayerUpdater->backingLayer();
if (layer) {
- DrawLayer(canvas->recordingContext(), canvas, layer, nullptr, nullptr, true);
+ SkRect srcRect = layer->getCurrentCropRect();
+ DrawLayer(canvas->recordingContext(), canvas, layer, &srcRect, nullptr, true);
}
}
@@ -67,6 +76,37 @@ static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, cons
isIntegerAligned(dstDevRect.y()));
}
+static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
+ const shaders::LinearEffect& linearEffect,
+ float maxDisplayLuminance,
+ float currentDisplayLuminanceNits,
+ float maxLuminance) {
+ auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+ auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
+ if (!runtimeEffect) {
+ LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+ }
+
+ SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
+
+ effectBuilder.child("child") = std::move(shader);
+
+ const auto uniforms = shaders::buildLinearEffectUniforms(
+ linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
+
+ for (const auto& uniform : uniforms) {
+ effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+ }
+
+ return effectBuilder.makeShader();
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+ const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
// TODO: Context arg probably doesn't belong here – do debug check at callsite instead.
bool LayerDrawable::DrawLayer(GrRecordingContext* context,
SkCanvas* canvas,
@@ -75,89 +115,108 @@ bool LayerDrawable::DrawLayer(GrRecordingContext* context,
const SkRect* dstRect,
bool useLayerTransform) {
if (context == nullptr) {
- SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+ ALOGD("Attempting to draw LayerDrawable into an unsupported surface");
return false;
}
// transform the matrix based on the layer
- SkMatrix layerTransform = layer->getTransform();
+ // SkMatrix layerTransform = layer->getTransform();
+ const uint32_t windowTransform = layer->getWindowTransform();
sk_sp<SkImage> layerImage = layer->getImage();
const int layerWidth = layer->getWidth();
const int layerHeight = layer->getHeight();
if (layerImage) {
- SkMatrix textureMatrixInv;
- textureMatrixInv = layer->getTexTransform();
- // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
- // use bottom left origin and remove flipV and invert transformations.
- SkMatrix flipV;
- flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
- textureMatrixInv.preConcat(flipV);
- textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight);
- textureMatrixInv.postScale(layerImage->width(), layerImage->height());
- SkMatrix textureMatrix;
- if (!textureMatrixInv.invert(&textureMatrix)) {
- textureMatrix = textureMatrixInv;
- }
+ const int imageWidth = layerImage->width();
+ const int imageHeight = layerImage->height();
- SkMatrix matrix;
if (useLayerTransform) {
- matrix = SkMatrix::Concat(layerTransform, textureMatrix);
- } else {
- matrix = textureMatrix;
+ canvas->save();
+ canvas->concat(layer->getTransform());
}
SkPaint paint;
paint.setAlpha(layer->getAlpha());
paint.setBlendMode(layer->getMode());
paint.setColorFilter(layer->getColorFilter());
- const bool nonIdentityMatrix = !matrix.isIdentity();
- if (nonIdentityMatrix) {
- canvas->save();
- canvas->concat(matrix);
- }
const SkMatrix& totalMatrix = canvas->getTotalMatrix();
- if (dstRect || srcRect) {
- SkMatrix matrixInv;
- if (!matrix.invert(&matrixInv)) {
- matrixInv = matrix;
- }
- SkRect skiaSrcRect;
- if (srcRect) {
- skiaSrcRect = *srcRect;
- } else {
- skiaSrcRect = SkRect::MakeIWH(layerWidth, layerHeight);
- }
- matrixInv.mapRect(&skiaSrcRect);
- SkRect skiaDestRect;
- if (dstRect) {
- skiaDestRect = *dstRect;
- } else {
- skiaDestRect = SkRect::MakeIWH(layerWidth, layerHeight);
- }
- matrixInv.mapRect(&skiaDestRect);
- // If (matrix is a rect-to-rect transform)
- // and (src/dst buffers size match in screen coordinates)
- // and (src/dst corners align fractionally),
- // then use nearest neighbor, otherwise use bilerp sampling.
- // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works
- // only for SrcOver blending and without color filter (readback uses Src blending).
- SkSamplingOptions sampling(SkFilterMode::kNearest);
- if (layer->getForceFilter() ||
- shouldFilterRect(totalMatrix, skiaSrcRect, skiaDestRect)) {
- sampling = SkSamplingOptions(SkFilterMode::kLinear);
- }
- canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- SkCanvas::kFast_SrcRectConstraint);
+ SkRect skiaSrcRect;
+ if (srcRect && !srcRect->isEmpty()) {
+ skiaSrcRect = *srcRect;
+ } else {
+ skiaSrcRect = SkRect::MakeIWH(imageWidth, imageHeight);
+ }
+ SkRect skiaDestRect;
+ if (dstRect && !dstRect->isEmpty()) {
+ skiaDestRect = (windowTransform & NATIVE_WINDOW_TRANSFORM_ROT_90)
+ ? SkRect::MakeIWH(dstRect->height(), dstRect->width())
+ : SkRect::MakeIWH(dstRect->width(), dstRect->height());
+ } else {
+ skiaDestRect = (windowTransform & NATIVE_WINDOW_TRANSFORM_ROT_90)
+ ? SkRect::MakeIWH(layerHeight, layerWidth)
+ : SkRect::MakeIWH(layerWidth, layerHeight);
+ }
+
+ const float px = skiaDestRect.centerX();
+ const float py = skiaDestRect.centerY();
+ SkMatrix m;
+ if (windowTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
+ m.postScale(-1.f, 1.f, px, py);
+ }
+ if (windowTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
+ m.postScale(1.f, -1.f, px, py);
+ }
+ if (windowTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
+ m.postRotate(90, 0, 0);
+ m.postTranslate(skiaDestRect.height(), 0);
+ }
+ auto constraint = SkCanvas::kFast_SrcRectConstraint;
+ if (srcRect && !srcRect->isEmpty()) {
+ constraint = SkCanvas::kStrict_SrcRectConstraint;
+ }
+
+ canvas->save();
+ canvas->concat(m);
+
+ // If (matrix is a rect-to-rect transform)
+ // and (src/dst buffers size match in screen coordinates)
+ // and (src/dst corners align fractionally),
+ // then use nearest neighbor, otherwise use bilerp sampling.
+ // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works
+ // only for SrcOver blending and without color filter (readback uses Src blending).
+ SkSamplingOptions sampling(SkFilterMode::kNearest);
+ if (layer->getForceFilter() || shouldFilterRect(totalMatrix, skiaSrcRect, skiaDestRect)) {
+ sampling = SkSamplingOptions(SkFilterMode::kLinear);
+ }
+
+ const auto sourceDataspace = static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
+ const SkImageInfo& imageInfo = canvas->imageInfo();
+ const auto destinationDataspace = static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
+
+ if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
+ const auto effect = shaders::LinearEffect{
+ .inputDataspace = sourceDataspace,
+ .outputDataspace = destinationDataspace,
+ .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
+ .fakeInputDataspace = destinationDataspace};
+ auto shader = layerImage->makeShader(sampling,
+ SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
+ constexpr float kMaxDisplayBrightess = 1000.f;
+ constexpr float kCurrentDisplayBrightness = 500.f;
+ shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
+ kCurrentDisplayBrightness,
+ layer->getMaxLuminanceNits());
+ paint.setShader(shader);
+ canvas->drawRect(skiaDestRect, paint);
} else {
- SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
- SkSamplingOptions sampling(SkFilterMode::kNearest);
- if (layer->getForceFilter() || shouldFilterRect(totalMatrix, imageRect, imageRect)) {
- sampling = SkSamplingOptions(SkFilterMode::kLinear);
- }
- canvas->drawImage(layerImage.get(), 0, 0, sampling, &paint);
+ canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+ constraint);
}
+
+ canvas->restore();
// restore the original matrix
- if (nonIdentityMatrix) {
+ if (useLayerTransform) {
canvas->restore();
}
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 48145d2331ee..507d3dcdcde9 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -88,6 +88,10 @@ static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect*
if (pendingClip) {
canvas->clipRect(*pendingClip);
}
+ const SkPath* path = outline.getPath();
+ if (path) {
+ canvas->clipPath(*path, SkClipOp::kIntersect, true);
+ }
return;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 9bca4df577c9..744739accb2c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -91,6 +91,8 @@ bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, con
fboInfo.fFormat = GL_RGBA8;
} else if (colorType == kRGBA_1010102_SkColorType) {
fboInfo.fFormat = GL_RGB10_A2;
+ } else if (colorType == kAlpha_8_SkColorType) {
+ fboInfo.fFormat = GL_R8;
} else {
LOG_ALWAYS_FATAL("Unsupported color type.");
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 4e7471d5d888..bc386feb2d6f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -613,6 +613,10 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020);
break;
+ case ColorMode::A8:
+ mSurfaceColorType = SkColorType::kAlpha_8_SkColorType;
+ mSurfaceColorSpace = nullptr;
+ break;
}
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 76c4a03d3a91..9c51e628e04a 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -187,28 +187,18 @@ void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) {
// kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
// older.
- if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) {
+ if (sApiLevel <= 27 && paint.asBlendMode() == SkBlendMode::kClear) {
paint.setBlendMode(SkBlendMode::kDstOut);
}
}
-static SkFilterMode Paint_to_filter(const SkPaint& paint) {
- return paint.getFilterQuality() != kNone_SkFilterQuality ? SkFilterMode::kLinear
- : SkFilterMode::kNearest;
-}
-
-static SkSamplingOptions Paint_to_sampling(const SkPaint& paint) {
- // Android only has 1-bit for "filter", so we don't try to cons-up mipmaps or cubics
- return SkSamplingOptions(Paint_to_filter(paint), SkMipmapMode::kNone);
-}
-
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
sk_sp<SkImage> image = bitmap.makeImage();
applyLooper(
paint,
- [&](const SkPaint& p) {
- mRecorder.drawImage(image, left, top, Paint_to_sampling(p), &p, bitmap.palette());
+ [&](const Paint& p) {
+ mRecorder.drawImage(image, left, top, p.sampling(), &p, bitmap.palette());
},
FilterForImage);
@@ -228,8 +218,8 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con
applyLooper(
paint,
- [&](const SkPaint& p) {
- mRecorder.drawImage(image, 0, 0, Paint_to_sampling(p), &p, bitmap.palette());
+ [&](const Paint& p) {
+ mRecorder.drawImage(image, 0, 0, p.sampling(), &p, bitmap.palette());
},
FilterForImage);
@@ -248,8 +238,8 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop
applyLooper(
paint,
- [&](const SkPaint& p) {
- mRecorder.drawImageRect(image, srcRect, dstRect, Paint_to_sampling(p), &p,
+ [&](const Paint& p) {
+ mRecorder.drawImageRect(image, srcRect, dstRect, p.sampling(), &p,
SkCanvas::kFast_SrcRectConstraint, bitmap.palette());
},
FilterForImage);
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 8abf4534a04c..e6ef95b9cf91 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -72,6 +72,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) {
.clip_top = mClip.fTop,
.clip_right = mClip.fRight,
.clip_bottom = mClip.fBottom,
+ .is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow,
};
mat4.getColMajor(&params.transform[0]);
params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index ddfb66f84f28..3c7617d35c7c 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -68,7 +68,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
ATRACE_CALL();
if (canvas->recordingContext() == nullptr) {
- SkDEBUGF(("Attempting to draw VkInteropFunctor into an unsupported surface"));
+ ALOGD("Attempting to draw VkInteropFunctor into an unsupported surface");
return;
}
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 4ae0f5a0a2e5..5c596576df4e 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -68,6 +68,9 @@ struct VkFunctorDrawParams {
int clip_top;
int clip_right;
int clip_bottom;
+
+ // Input: Whether destination surface is offscreen surface.
+ bool is_layer;
};
} // namespace uirenderer
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a066e6f7c693..122c77f3dadc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -18,15 +18,16 @@
#include <apex/window.h>
#include <fcntl.h>
+#include <gui/TraceUtils.h>
#include <strings.h>
#include <sys/stat.h>
+#include <ui/Fence.h>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <functional>
-#include <gui/TraceUtils.h>
#include "../Properties.h"
#include "AnimationContext.h"
#include "Frame.h"
@@ -203,9 +204,10 @@ void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
mSurfaceControl = surfaceControl;
mSurfaceControlGenerationId++;
mExpectSurfaceStats = surfaceControl != nullptr;
- if (mSurfaceControl != nullptr) {
+ if (mExpectSurfaceStats) {
funcs.acquireFunc(mSurfaceControl);
- funcs.registerListenerFunc(surfaceControl, this, &onSurfaceStatsAvailable);
+ funcs.registerListenerFunc(surfaceControl, mSurfaceControlGenerationId, this,
+ &onSurfaceStatsAvailable);
}
}
@@ -218,7 +220,7 @@ void CanvasContext::setupPipelineSurface() {
}
- mFrameNumber = -1;
+ mFrameNumber = 0;
if (mNativeSurface != nullptr && hasSurface) {
mHaveNewSurface = true;
@@ -256,7 +258,7 @@ void CanvasContext::setStopped(bool stopped) {
}
void CanvasContext::allocateBuffers() {
- if (mNativeSurface) {
+ if (mNativeSurface && Properties::isDrawingEnabled()) {
ANativeWindow_tryAllocateBuffers(mNativeSurface->getNativeWindow());
}
}
@@ -388,7 +390,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
return;
}
- if (CC_LIKELY(mSwapHistory.size() && !Properties::forceDrawFrame)) {
+ if (CC_LIKELY(mSwapHistory.size() && !info.forceDrawFrame)) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
SwapHistory& lastSwap = mSwapHistory.back();
nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
@@ -480,7 +482,8 @@ nsecs_t CanvasContext::draw() {
SkRect dirty;
mDamageAccumulator.finish(&dirty);
- if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
+ if (!Properties::isDrawingEnabled() ||
+ (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
@@ -507,11 +510,13 @@ nsecs_t CanvasContext::draw() {
Frame frame = mRenderPipeline->getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
+ ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
+
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
- int64_t frameCompleteNr = getFrameNumber();
+ uint64_t frameCompleteNr = getFrameNumber();
waitOnFences();
@@ -521,8 +526,9 @@ nsecs_t CanvasContext::draw() {
if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
- native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
- inputEventId);
+ native_window_set_frame_timeline_info(
+ mNativeSurface->getNativeWindow(), vsyncId, inputEventId,
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime));
}
}
@@ -579,7 +585,7 @@ nsecs_t CanvasContext::draw() {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
mHaveNewSurface = false;
- mFrameNumber = -1;
+ mFrameNumber = 0;
} else {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
@@ -612,16 +618,20 @@ nsecs_t CanvasContext::draw() {
if (requireSwap) {
if (mExpectSurfaceStats) {
reportMetricsWithPresentTime();
- std::lock_guard lock(mLast4FrameInfosMutex);
- std::pair<FrameInfo*, int64_t>& next = mLast4FrameInfos.next();
- next.first = mCurrentFrameInfo;
- next.second = frameCompleteNr;
+ { // acquire lock
+ std::lock_guard lock(mLast4FrameMetricsInfosMutex);
+ FrameMetricsInfo& next = mLast4FrameMetricsInfos.next();
+ next.frameInfo = mCurrentFrameInfo;
+ next.frameNumber = frameCompleteNr;
+ next.surfaceId = mSurfaceControlGenerationId;
+ } // release lock
} else {
mCurrentFrameInfo->markFrameCompleted();
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
std::scoped_lock lock(mFrameMetricsReporterMutex);
- mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter);
+ mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
+ mSurfaceControlGenerationId);
}
}
@@ -657,14 +667,18 @@ void CanvasContext::reportMetricsWithPresentTime() {
ATRACE_CALL();
FrameInfo* forthBehind;
int64_t frameNumber;
+ int32_t surfaceControlId;
+
{ // acquire lock
- std::scoped_lock lock(mLast4FrameInfosMutex);
- if (mLast4FrameInfos.size() != mLast4FrameInfos.capacity()) {
+ std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
+ if (mLast4FrameMetricsInfos.size() != mLast4FrameMetricsInfos.capacity()) {
// Not enough frames yet
return;
}
- // Surface object keeps stats for the last 8 frames.
- std::tie(forthBehind, frameNumber) = mLast4FrameInfos.front();
+ auto frameMetricsInfo = mLast4FrameMetricsInfos.front();
+ forthBehind = frameMetricsInfo.frameInfo;
+ frameNumber = frameMetricsInfo.frameNumber;
+ surfaceControlId = frameMetricsInfo.surfaceId;
} // release lock
nsecs_t presentTime = 0;
@@ -679,40 +693,70 @@ void CanvasContext::reportMetricsWithPresentTime() {
{ // acquire lock
std::scoped_lock lock(mFrameMetricsReporterMutex);
if (mFrameMetricsReporter != nullptr) {
- mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/);
+ mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
+ frameNumber, surfaceControlId);
}
} // release lock
}
-FrameInfo* CanvasContext::getFrameInfoFromLast4(uint64_t frameNumber) {
- std::scoped_lock lock(mLast4FrameInfosMutex);
- for (size_t i = 0; i < mLast4FrameInfos.size(); i++) {
- if (mLast4FrameInfos[i].second == frameNumber) {
- return mLast4FrameInfos[i].first;
+void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
+ std::scoped_lock lock(mFrameMetricsReporterMutex);
+ if (mFrameMetricsReporter.get() == nullptr) {
+ mFrameMetricsReporter.reset(new FrameMetricsReporter());
+ }
+
+ // We want to make sure we aren't reporting frames that have already been queued by the
+ // BufferQueueProducer on the rendner thread but are still pending the callback to report their
+ // their frame metrics.
+ uint64_t nextFrameNumber = getFrameNumber();
+ observer->reportMetricsFrom(nextFrameNumber, mSurfaceControlGenerationId);
+ mFrameMetricsReporter->addObserver(observer);
+}
+
+void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+ std::scoped_lock lock(mFrameMetricsReporterMutex);
+ if (mFrameMetricsReporter.get() != nullptr) {
+ mFrameMetricsReporter->removeObserver(observer);
+ if (!mFrameMetricsReporter->hasObservers()) {
+ mFrameMetricsReporter.reset(nullptr);
}
}
- return nullptr;
}
-void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
- ASurfaceControlStats* stats) {
+FrameInfo* CanvasContext::getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId) {
+ std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
+ for (size_t i = 0; i < mLast4FrameMetricsInfos.size(); i++) {
+ if (mLast4FrameMetricsInfos[i].frameNumber == frameNumber &&
+ mLast4FrameMetricsInfos[i].surfaceId == surfaceControlId) {
+ return mLast4FrameMetricsInfos[i].frameInfo;
+ }
+ }
+
+ return nullptr;
+}
- CanvasContext* instance = static_cast<CanvasContext*>(context);
+void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
+ ASurfaceControlStats* stats) {
+ auto* instance = static_cast<CanvasContext*>(context);
const ASurfaceControlFunctions& functions =
instance->mRenderThread.getASurfaceControlFunctions();
nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
+ if (gpuCompleteTime == Fence::SIGNAL_TIME_PENDING) {
+ gpuCompleteTime = -1;
+ }
uint64_t frameNumber = functions.getFrameNumberFunc(stats);
- FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber);
+ FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
frameInfo->set(FrameInfoIndex::GpuCompleted) = gpuCompleteTime;
std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
- instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter);
+ instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
+ surfaceControlId);
}
}
@@ -853,9 +897,9 @@ void CanvasContext::enqueueFrameWork(std::function<void()>&& func) {
mFrameFences.push_back(CommonPool::async(std::move(func)));
}
-int64_t CanvasContext::getFrameNumber() {
- // mFrameNumber is reset to -1 when the surface changes or we swap buffers
- if (mFrameNumber == -1 && mNativeSurface.get()) {
+uint64_t CanvasContext::getFrameNumber() {
+ // mFrameNumber is reset to 0 when the surface changes or we swap buffers
+ if (mFrameNumber == 0 && mNativeSurface.get()) {
mFrameNumber = ANativeWindow_getNextFrameId(mNativeSurface->getNativeWindow());
}
return mFrameNumber;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 389546ea7973..951ee216ce35 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -90,9 +90,17 @@ public:
* and false otherwise (e.g. cache limits have been exceeded).
*/
bool pinImages(std::vector<SkImage*>& mutableImages) {
+ if (!Properties::isDrawingEnabled()) {
+ return true;
+ }
return mRenderPipeline->pinImages(mutableImages);
}
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) { return mRenderPipeline->pinImages(images); }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) {
+ if (!Properties::isDrawingEnabled()) {
+ return true;
+ }
+ return mRenderPipeline->pinImages(images);
+ }
/**
* Unpin any image that had be previously pinned to the GPU cache
@@ -159,29 +167,13 @@ public:
void setContentDrawBounds(const Rect& bounds) { mContentDrawBounds = bounds; }
- void addFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
- if (mFrameMetricsReporter.get() == nullptr) {
- mFrameMetricsReporter.reset(new FrameMetricsReporter());
- }
-
- mFrameMetricsReporter->addObserver(observer);
- }
-
- void removeFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
- if (mFrameMetricsReporter.get() != nullptr) {
- mFrameMetricsReporter->removeObserver(observer);
- if (!mFrameMetricsReporter->hasObservers()) {
- mFrameMetricsReporter.reset(nullptr);
- }
- }
- }
+ void addFrameMetricsObserver(FrameMetricsObserver* observer);
+ void removeFrameMetricsObserver(FrameMetricsObserver* observer);
// Used to queue up work that needs to be completed before this frame completes
void enqueueFrameWork(std::function<void()>&& func);
- int64_t getFrameNumber();
+ uint64_t getFrameNumber();
void waitOnFences();
@@ -204,8 +196,8 @@ public:
SkISize getNextFrameSize() const;
// Called when SurfaceStats are available.
- static void onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
- ASurfaceControlStats* stats);
+ static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
+ ASurfaceControlStats* stats);
void setASurfaceTransactionCallback(
const std::function<bool(int64_t, int64_t, int64_t)>& callback) {
@@ -246,7 +238,13 @@ private:
*/
void reportMetricsWithPresentTime();
- FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber);
+ struct FrameMetricsInfo {
+ FrameInfo* frameInfo;
+ int64_t frameNumber;
+ int32_t surfaceId;
+ };
+
+ FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
// The same type as Frame.mWidth and Frame.mHeight
int32_t mLastFrameWidth = 0;
@@ -258,7 +256,10 @@ private:
// NULL to remove the reference
ASurfaceControl* mSurfaceControl = nullptr;
// id to track surface control changes and WebViewFunctor uses it to determine
- // whether reparenting is needed
+ // whether reparenting is needed also used by FrameMetricsReporter to determine
+ // if a frame is from an "old" surface (i.e. one that existed before the
+ // observer was attched) and therefore shouldn't be reported.
+ // NOTE: It is important that this is an increasing counter.
int32_t mSurfaceControlGenerationId = 0;
// stopped indicates the CanvasContext will reject actual redraw operations,
// and defer repaint until it is un-stopped
@@ -278,9 +279,10 @@ private:
nsecs_t queueDuration;
};
- // Need at least 4 because we do quad buffer. Add a 5th for good measure.
+ // Need at least 4 because we do quad buffer. Add a few more for good measure.
RingBuffer<SwapHistory, 7> mSwapHistory;
- int64_t mFrameNumber = -1;
+ // Frame numbers start at 1, 0 means uninitialized
+ uint64_t mFrameNumber = 0;
int64_t mDamageId = 0;
// last vsync for a dropped frame due to stuffed queue
@@ -300,10 +302,11 @@ private:
FrameInfo* mCurrentFrameInfo = nullptr;
- // List of frames that are awaiting GPU completion reporting
- RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos
- GUARDED_BY(mLast4FrameInfosMutex);
- std::mutex mLast4FrameInfosMutex;
+ // List of data of frames that are awaiting GPU completion reporting. Used to compute frame
+ // metrics and determine whether or not to report the metrics.
+ RingBuffer<FrameMetricsInfo, 4> mLast4FrameMetricsInfos
+ GUARDED_BY(mLast4FrameMetricsInfosMutex);
+ std::mutex mLast4FrameMetricsInfosMutex;
std::string mName;
JankTracker mJankTracker;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 94aedd0f43be..59c914f0198c 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -133,6 +133,7 @@ int DrawFrameTask::drawFrame() {
}
void DrawFrameTask::postAndWait() {
+ ATRACE_CALL();
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
@@ -147,6 +148,8 @@ void DrawFrameTask::run() {
bool canDrawThisFrame;
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
+ info.forceDrawFrame = mForceDrawFrame;
+ mForceDrawFrame = false;
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
@@ -158,7 +161,8 @@ void DrawFrameTask::run() {
// Grab a copy of everything we need
CanvasContext* context = mContext;
- std::function<void(int64_t)> frameCallback = std::move(mFrameCallback);
+ std::function<std::function<void(bool)>(int32_t, int64_t)> frameCallback =
+ std::move(mFrameCallback);
std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
mFrameCallback = nullptr;
mFrameCompleteCallback = nullptr;
@@ -173,8 +177,13 @@ void DrawFrameTask::run() {
// Even if we aren't drawing this vsync pulse the next frame number will still be accurate
if (CC_UNLIKELY(frameCallback)) {
- context->enqueueFrameWork(
- [frameCallback, frameNr = context->getFrameNumber()]() { frameCallback(frameNr); });
+ context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
+ frameNr = context->getFrameNumber()]() {
+ auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
+ if (frameCommitCallback) {
+ context->addFrameCommitListener(std::move(frameCommitCallback));
+ }
+ });
}
nsecs_t dequeueBufferDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index e3ea802b07b9..d6fc292d5900 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
#include "../FrameInfo.h"
#include "../Rect.h"
#include "../TreeInfo.h"
+#include "RenderTask.h"
namespace android {
namespace uirenderer {
@@ -77,7 +76,7 @@ public:
void run();
- void setFrameCallback(std::function<void(int64_t)>&& callback) {
+ void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
mFrameCallback = std::move(callback);
}
@@ -89,6 +88,8 @@ public:
mFrameCompleteCallback = std::move(callback);
}
+ void forceDrawNextFrame() { mForceDrawFrame = true; }
+
private:
class HintSessionWrapper {
public:
@@ -126,13 +127,15 @@ private:
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
- std::function<void(int64_t)> mFrameCallback;
+ std::function<std::function<void(bool)>(int32_t, int64_t)> mFrameCallback;
std::function<void(bool)> mFrameCommitCallback;
std::function<void()> mFrameCompleteCallback;
nsecs_t mLastDequeueBufferDuration = 0;
nsecs_t mLastTargetWorkDuration = 0;
std::optional<HintSessionWrapper> mHintSessionWrapper;
+
+ bool mForceDrawFrame = false;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c7d7a17a23eb..02257db9df6a 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -90,6 +90,7 @@ EglManager::EglManager()
, mEglConfig(nullptr)
, mEglConfigF16(nullptr)
, mEglConfig1010102(nullptr)
+ , mEglConfigA8(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
, mCurrentSurface(EGL_NO_SURFACE)
@@ -246,6 +247,50 @@ EGLConfig EglManager::loadFP16Config(EGLDisplay display, SwapBehavior swapBehavi
return config;
}
+EGLConfig EglManager::loadA8Config(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
+ EGLint eglSwapBehavior =
+ (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+ EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE,
+ 8,
+ EGL_GREEN_SIZE,
+ 0,
+ EGL_BLUE_SIZE,
+ 0,
+ EGL_ALPHA_SIZE,
+ 0,
+ EGL_DEPTH_SIZE,
+ 0,
+ EGL_SURFACE_TYPE,
+ EGL_WINDOW_BIT | eglSwapBehavior,
+ EGL_NONE};
+ EGLint numConfigs = 1;
+ if (!eglChooseConfig(display, attribs, nullptr, numConfigs, &numConfigs)) {
+ return EGL_NO_CONFIG_KHR;
+ }
+
+ std::vector<EGLConfig> configs(numConfigs, EGL_NO_CONFIG_KHR);
+ if (!eglChooseConfig(display, attribs, configs.data(), numConfigs, &numConfigs)) {
+ return EGL_NO_CONFIG_KHR;
+ }
+
+ // The component sizes passed to eglChooseConfig are minimums, so configs
+ // contains entries that exceed them. Choose one that matches the sizes
+ // exactly.
+ for (EGLConfig config : configs) {
+ EGLint r{0}, g{0}, b{0}, a{0};
+ eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r);
+ eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g);
+ eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b);
+ eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a);
+ if (8 == r && 0 == g && 0 == b && 0 == a) {
+ return config;
+ }
+ }
+ return EGL_NO_CONFIG_KHR;
+}
+
void EglManager::initExtensions() {
auto extensions = StringUtils::split(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
@@ -345,10 +390,14 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
sk_sp<SkColorSpace> colorSpace) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
- if (!mHasWideColorGamutSupport || !EglExtensions.noConfigContext) {
+ if (!EglExtensions.noConfigContext) {
+ // The caller shouldn't use A8 if we cannot switch modes.
+ LOG_ALWAYS_FATAL_IF(colorMode == ColorMode::A8,
+ "Cannot use A8 without EGL_KHR_no_config_context!");
+
+ // Cannot switch modes without EGL_KHR_no_config_context.
colorMode = ColorMode::Default;
}
-
// The color space we want to use depends on whether linear blending is turned
// on and whether the app has requested wide color gamut rendering. When wide
// color gamut rendering is off, the app simply renders in the display's native
@@ -374,42 +423,61 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE};
EGLConfig config = mEglConfig;
- if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
- if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
+ if (colorMode == ColorMode::A8) {
+ // A8 doesn't use a color space
+ if (!mEglConfigA8) {
+ mEglConfigA8 = loadA8Config(mEglDisplay, mSwapBehavior);
+ LOG_ALWAYS_FATAL_IF(!mEglConfigA8,
+ "Requested ColorMode::A8, but EGL lacks support! error = %s",
+ eglErrorString());
+ }
+ config = mEglConfigA8;
+ } else {
+ if (!mHasWideColorGamutSupport) {
colorMode = ColorMode::Default;
- } else {
- config = mEglConfigF16;
}
- }
- if (EglExtensions.glColorSpace) {
- attribs[0] = EGL_GL_COLORSPACE_KHR;
- switch (colorMode) {
- case ColorMode::Default:
- attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
- break;
- case ColorMode::WideColorGamut: {
- skcms_Matrix3x3 colorGamut;
- LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
- "Could not get gamut matrix from color space");
- if (memcmp(&colorGamut, &SkNamedGamut::kDisplayP3, sizeof(colorGamut)) == 0) {
- attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
- } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
- attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
- } else if (memcmp(&colorGamut, &SkNamedGamut::kRec2020, sizeof(colorGamut)) == 0) {
- attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
- } else {
- LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+
+ if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+ if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
+ colorMode = ColorMode::Default;
+ } else {
+ config = mEglConfigF16;
+ }
+ }
+ if (EglExtensions.glColorSpace) {
+ attribs[0] = EGL_GL_COLORSPACE_KHR;
+ switch (colorMode) {
+ case ColorMode::Default:
+ attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
+ break;
+ case ColorMode::WideColorGamut: {
+ skcms_Matrix3x3 colorGamut;
+ LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
+ "Could not get gamut matrix from color space");
+ if (memcmp(&colorGamut, &SkNamedGamut::kDisplayP3, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ } else if (memcmp(&colorGamut, &SkNamedGamut::kRec2020, sizeof(colorGamut)) ==
+ 0) {
+ attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+ } else {
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ }
+ break;
}
- break;
+ case ColorMode::Hdr:
+ config = mEglConfigF16;
+ attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+ break;
+ case ColorMode::Hdr10:
+ config = mEglConfig1010102;
+ attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+ break;
+ case ColorMode::A8:
+ LOG_ALWAYS_FATAL("Unreachable: A8 doesn't use a color space");
+ break;
}
- case ColorMode::Hdr:
- config = mEglConfigF16;
- attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
- break;
- case ColorMode::Hdr10:
- config = mEglConfig1010102;
- attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
- break;
}
}
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 69f3ed014c53..fc6b28d2e1ad 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -89,6 +89,7 @@ private:
static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior);
static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior);
static EGLConfig load1010102Config(EGLDisplay display, SwapBehavior swapBehavior);
+ static EGLConfig loadA8Config(EGLDisplay display, SwapBehavior swapBehavior);
void initExtensions();
void createPBufferSurface();
@@ -100,6 +101,7 @@ private:
EGLConfig mEglConfig;
EGLConfig mEglConfigF16;
EGLConfig mEglConfig1010102;
+ EGLConfig mEglConfigA8;
EGLContext mEglContext;
EGLSurface mPBufferSurface;
EGLSurface mCurrentSurface;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 72d4ac5081e6..a44b498c81c1 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -133,6 +133,10 @@ int64_t* RenderProxy::frameInfo() {
return mDrawFrameTask.frameInfo();
}
+void RenderProxy::forceDrawNextFrame() {
+ mDrawFrameTask.forceDrawNextFrame();
+}
+
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
@@ -257,10 +261,15 @@ uint32_t RenderProxy::frameTimePercentile(int percentile) {
});
}
-void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData) {
+void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData, bool resetProfile) {
if (RenderThread::hasInstance()) {
auto& thread = RenderThread::getInstance();
- thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd, includeProfileData); });
+ thread.queue().runSync([&]() {
+ thread.dumpGraphicsMemory(fd, includeProfileData);
+ if (resetProfile) {
+ thread.globalProfileData()->reset();
+ }
+ });
}
}
@@ -322,7 +331,8 @@ void RenderProxy::setPrepareSurfaceControlForWebviewCallback(
[this, cb = callback]() { mContext->setPrepareSurfaceControlForWebviewCallback(cb); });
}
-void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
+void RenderProxy::setFrameCallback(
+ std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
mDrawFrameTask.setFrameCallback(std::move(callback));
}
@@ -418,6 +428,15 @@ void RenderProxy::preload() {
thread.queue().post([&thread]() { thread.preload(); });
}
+void RenderProxy::setRtAnimationsEnabled(bool enabled) {
+ if (RenderThread::hasInstance()) {
+ RenderThread::getInstance().queue().post(
+ [enabled]() { Properties::enableRTAnimations = enabled; });
+ } else {
+ Properties::enableRTAnimations = enabled;
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 6417b38df064..ee9efd46e307 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -82,6 +82,7 @@ public:
void setOpaque(bool opaque);
void setColorMode(ColorMode mode);
int64_t* frameInfo();
+ void forceDrawNextFrame();
int syncAndDrawFrame();
void destroy();
@@ -108,7 +109,8 @@ public:
// Not exported, only used for testing
void resetProfileInfo();
uint32_t frameTimePercentile(int p);
- static void dumpGraphicsMemory(int fd, bool includeProfileData = true);
+ static void dumpGraphicsMemory(int fd, bool includeProfileData = true,
+ bool resetProfile = false);
static void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
static void rotateProcessStatsBuffer();
@@ -123,7 +125,7 @@ public:
void setASurfaceTransactionCallback(
const std::function<bool(int64_t, int64_t, int64_t)>& callback);
void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback);
- void setFrameCallback(std::function<void(int64_t)>&& callback);
+ void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback);
void setFrameCommitCallback(std::function<void(bool)>&& callback);
void setFrameCompleteCallback(std::function<void()>&& callback);
@@ -142,6 +144,8 @@ public:
static void preload();
+ static void setRtAnimationsEnabled(bool enabled);
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index f83c0a4926f9..01b956cb3dd5 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -16,8 +16,21 @@
#include "RenderThread.h"
+#include <GrContextOptions.h>
+#include <android-base/properties.h>
+#include <dlfcn.h>
+#include <gl/GrGLInterface.h>
#include <gui/TraceUtils.h>
+#include <sys/resource.h>
+#include <ui/FatVector.h>
+#include <utils/Condition.h>
+#include <utils/Log.h>
+#include <utils/Mutex.h>
+
+#include <thread>
+
#include "../HardwareBitmapUploader.h"
+#include "CacheManager.h"
#include "CanvasContext.h"
#include "DeviceInfo.h"
#include "EglManager.h"
@@ -31,19 +44,6 @@
#include "renderstate/RenderState.h"
#include "utils/TimeUtils.h"
-#include <GrContextOptions.h>
-#include <gl/GrGLInterface.h>
-
-#include <dlfcn.h>
-#include <sys/resource.h>
-#include <utils/Condition.h>
-#include <utils/Log.h>
-#include <utils/Mutex.h>
-#include <thread>
-
-#include <android-base/properties.h>
-#include <ui/FatVector.h>
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -112,18 +112,31 @@ ASurfaceControlFunctions::ASurfaceControlFunctions() {
"Failed to find required symbol ASurfaceTransaction_setZOrder!");
}
-void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
+void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* cbData,
+ void* data) {
RenderThread* rt = reinterpret_cast<RenderThread*>(data);
- int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
- int64_t frameDeadline = AChoreographer_getFrameDeadline(rt->mChoreographer);
+ size_t preferredFrameTimelineIndex =
+ AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
+ AVsyncId vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+ cbData, preferredFrameTimelineIndex);
+ int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
+ cbData, preferredFrameTimelineIndex);
+ int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData);
+ // TODO(b/193273294): Remove when shared memory in use w/ expected present time always current.
int64_t frameInterval = AChoreographer_getFrameInterval(rt->mChoreographer);
- rt->mVsyncRequested = false;
- if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId, frameDeadline,
- frameInterval) && !rt->mFrameCallbackTaskPending) {
+ rt->frameCallback(vsyncId, frameDeadline, frameTimeNanos, frameInterval);
+}
+
+void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t frameTimeNanos,
+ int64_t frameInterval) {
+ mVsyncRequested = false;
+ if (timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId, frameDeadline,
+ frameInterval) &&
+ !mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
- rt->mFrameCallbackTaskPending = true;
- nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
- rt->queue().postAt(runAt, [=]() { rt->dispatchFrameCallbacks(); });
+ mFrameCallbackTaskPending = true;
+ nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+ queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
}
}
@@ -139,8 +152,8 @@ public:
ChoreographerSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
virtual void requestNextVsync() override {
- AChoreographer_postFrameCallback64(mRenderThread->mChoreographer,
- RenderThread::frameCallback, mRenderThread);
+ AChoreographer_postVsyncCallback(mRenderThread->mChoreographer,
+ RenderThread::extendedFrameCallback, mRenderThread);
}
virtual void drainPendingEvents() override {
@@ -157,12 +170,16 @@ public:
virtual void requestNextVsync() override {
mRenderThread->queue().postDelayed(16_ms, [this]() {
- RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+ mRenderThread->frameCallback(UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ std::numeric_limits<int64_t>::max(),
+ systemTime(SYSTEM_TIME_MONOTONIC), 16_ms);
});
}
virtual void drainPendingEvents() override {
- RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+ mRenderThread->frameCallback(UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ std::numeric_limits<int64_t>::max(),
+ systemTime(SYSTEM_TIME_MONOTONIC), 16_ms);
}
private:
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 05d225b856db..c1f6790b25b2 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -83,8 +83,9 @@ typedef ASurfaceControl* (*ASC_create)(ASurfaceControl* parent, const char* debu
typedef void (*ASC_acquire)(ASurfaceControl* control);
typedef void (*ASC_release)(ASurfaceControl* control);
-typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, void* context,
- ASurfaceControl_SurfaceStatsListener func);
+typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, int32_t id,
+ void* context,
+ ASurfaceControl_SurfaceStatsListener func);
typedef void (*ASC_unregisterSurfaceStatsListener)(void* context,
ASurfaceControl_SurfaceStatsListener func);
@@ -210,7 +211,9 @@ private:
// corresponding callbacks for each display event type
static int choreographerCallback(int fd, int events, void* data);
// Callback that will be run on vsync ticks.
- static void frameCallback(int64_t frameTimeNanos, void* data);
+ static void extendedFrameCallback(const AChoreographerFrameCallbackData* cbData, void* data);
+ void frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t frameTimeNanos,
+ int64_t frameInterval);
// Callback that will be run whenver there is a refresh rate change.
static void refreshRateCallback(int64_t vsyncPeriod, void* data);
void drainDisplayEventQueue();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 9e8a1e141fe1..a9ff2c60fdbe 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -35,6 +35,9 @@
#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
+#undef LOG_TAG
+#define LOG_TAG "VulkanManager"
+
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index fe9a30a59870..7dd3561cb220 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -24,6 +24,9 @@
#include "VulkanManager.h"
#include "utils/Color.h"
+#undef LOG_TAG
+#define LOG_TAG "VulkanSurface"
+
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -197,8 +200,9 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType);
outWindowInfo->colorspace = colorSpace;
outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
- LOG_ALWAYS_FATAL_IF(outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN,
- "Unsupported colorspace");
+ LOG_ALWAYS_FATAL_IF(
+ outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN && colorType != kAlpha_8_SkColorType,
+ "Unsupported colorspace");
VkFormat vkPixelFormat;
switch (colorType) {
@@ -211,6 +215,9 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
case kRGBA_1010102_SkColorType:
vkPixelFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
break;
+ case kAlpha_8_SkColorType:
+ vkPixelFormat = VK_FORMAT_R8_UNORM;
+ break;
default:
LOG_ALWAYS_FATAL("Unsupported colorType: %d", (int)colorType);
}
@@ -426,7 +433,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
if (bufferInfo->skSurface.get() == nullptr) {
bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
- kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr);
+ kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index e8ba15fe92af..491af4336f97 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -74,7 +74,7 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
layerUpdater->setTransform(&transform);
// updateLayer so it's ready to draw
- layerUpdater->updateLayer(true, SkMatrix::I(), nullptr);
+ layerUpdater->updateLayer(true, nullptr, 0, SkRect::MakeEmpty());
return layerUpdater;
}
diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
new file mode 100644
index 000000000000..1e343c1dd283
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "TestSceneBase.h"
+
+class PathClippingAnimation : public TestScene {
+public:
+ int mSpacing, mSize;
+ bool mClip, mAnimateClip;
+ int mMaxCards;
+ std::vector<sp<RenderNode> > cards;
+
+ PathClippingAnimation(int spacing, int size, bool clip, bool animateClip, int maxCards)
+ : mSpacing(spacing)
+ , mSize(size)
+ , mClip(clip)
+ , mAnimateClip(animateClip)
+ , mMaxCards(maxCards) {}
+
+ PathClippingAnimation(int spacing, int size, bool clip, bool animateClip)
+ : PathClippingAnimation(spacing, size, clip, animateClip, INT_MAX) {}
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
+ canvas.enableZ(true);
+ int ci = 0;
+ int numCards = 0;
+
+ for (int x = 0; x < width; x += mSpacing) {
+ for (int y = 0; y < height; y += mSpacing) {
+ auto color = BrightColors[ci++ % BrightColorsCount];
+ auto card = TestUtils::createNode(
+ x, y, x + mSize, y + mSize, [&](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(color, SkBlendMode::kSrcOver);
+ if (mClip) {
+ // Create circular path that rounds around the inside of all
+ // four corners of the given square defined by mSize*mSize
+ SkPath path = setPath(mSize);
+ props.mutableOutline().setPath(&path, 1);
+ props.mutableOutline().setShouldClip(true);
+ }
+ });
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ ++numCards;
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+
+ canvas.enableZ(false);
+ }
+
+ SkPath setPath(int size) {
+ SkPath path;
+ path.moveTo(0, size / 2);
+ path.cubicTo(0, size * .75, size * .25, size, size / 2, size);
+ path.cubicTo(size * .75, size, size, size * .75, size, size / 2);
+ path.cubicTo(size, size * .25, size * .75, 0, size / 2, 0);
+ path.cubicTo(size / 4, 0, 0, size / 4, 0, size / 2);
+ return path;
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 50;
+ if (curFrame > 25) curFrame = 50 - curFrame;
+ for (auto& card : cards) {
+ if (mAnimateClip) {
+ SkPath path = setPath(mSize - curFrame);
+ card->mutateStagingProperties().mutableOutline().setPath(&path, 1);
+ }
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::DISPLAY_LIST);
+ }
+ }
+};
+
+static TestScene::Registrar _PathClippingUnclipped(TestScene::Info{
+ "pathClipping-unclipped", "Multiple RenderNodes, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), false, false);
+ }});
+
+static TestScene::Registrar _PathClippingUnclippedSingle(TestScene::Info{
+ "pathClipping-unclippedsingle", "A single RenderNode, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), false, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingUnclippedSingleLarge(TestScene::Info{
+ "pathClipping-unclippedsinglelarge", "A single large RenderNode, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), false, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingClipped80(TestScene::Info{
+ "pathClipping-clipped80", "Multiple RenderNodes, clipped by paths.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, false);
+ }});
+
+static TestScene::Registrar _PathClippingClippedSingle(TestScene::Info{
+ "pathClipping-clippedsingle", "A single RenderNode, clipped by a path.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingClippedSingleLarge(TestScene::Info{
+ "pathClipping-clippedsinglelarge", "A single large RenderNode, clipped by a path.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), true, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingAnimated(TestScene::Info{
+ "pathClipping-animated",
+ "Multiple RenderNodes, clipped by paths which are being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, true);
+ }});
+
+static TestScene::Registrar _PathClippingAnimatedSingle(TestScene::Info{
+ "pathClipping-animatedsingle",
+ "A single RenderNode, clipped by a path which is being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, true, 1);
+ }});
+
+static TestScene::Registrar _PathClippingAnimatedSingleLarge(TestScene::Info{
+ "pathClipping-animatedsinglelarge",
+ "A single large RenderNode, clipped by a path which is being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), true, true, 1);
+ }});
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index 163745b04ed2..e9f353d887f2 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -21,14 +21,17 @@
class RoundRectClippingAnimation : public TestScene {
public:
int mSpacing, mSize;
+ int mMaxCards;
- RoundRectClippingAnimation(int spacing, int size) : mSpacing(spacing), mSize(size) {}
+ RoundRectClippingAnimation(int spacing, int size, int maxCards = INT_MAX)
+ : mSpacing(spacing), mSize(size), mMaxCards(maxCards) {}
std::vector<sp<RenderNode> > cards;
void createContent(int width, int height, Canvas& canvas) override {
canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.enableZ(true);
int ci = 0;
+ int numCards = 0;
for (int x = 0; x < width; x += mSpacing) {
for (int y = 0; y < height; y += mSpacing) {
@@ -42,6 +45,13 @@ public:
});
canvas.drawRenderNode(card.get());
cards.push_back(card);
+ ++numCards;
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+ if (numCards >= mMaxCards) {
+ break;
}
}
@@ -71,3 +81,22 @@ static TestScene::Registrar _RoundRectClippingCpu(TestScene::Info{
[](const TestScene::Options&) -> test::TestScene* {
return new RoundRectClippingAnimation(dp(20), dp(20));
}});
+
+static TestScene::Registrar _RoundRectClippingGrid(TestScene::Info{
+ "roundRectClipping-grid", "A grid of RenderNodes with round rect clipping outlines.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(80));
+ }});
+
+static TestScene::Registrar _RoundRectClippingSingle(TestScene::Info{
+ "roundRectClipping-single", "A single RenderNodes with round rect clipping outline.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(80), 1);
+ }});
+
+static TestScene::Registrar _RoundRectClippingSingleLarge(TestScene::Info{
+ "roundRectClipping-singlelarge",
+ "A single large RenderNodes with round rect clipping outline.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(350), 1);
+ }});
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
index 10ba07905c45..31a8ae1d38cd 100644
--- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -49,7 +49,7 @@ public:
paint.setAntiAlias(true);
paint.setColor(Color::Green_700);
canvas.drawCircle(200, 200, 200, paint);
- SkPaint alphaPaint;
+ Paint alphaPaint;
alphaPaint.setAlpha(128);
canvas.restoreUnclippedLayer(unclippedSaveLayer, alphaPaint);
canvas.restore();
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index de2c6214088d..613a6aee3a5b 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -104,7 +104,6 @@ static void doRun(const TestScene::Info& info, const TestScene::Options& opts, i
// If we're reporting GPU memory usage we need to first start with a clean slate
RenderProxy::purgeCaches();
}
- Properties::forceDrawFrame = true;
TestContext testContext;
testContext.setRenderOffscreen(opts.renderOffscreen);
@@ -144,6 +143,7 @@ static void doRun(const TestScene::Info& info, const TestScene::Options& opts, i
.setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
UiFrameInfoBuilder::UNKNOWN_DEADLINE,
UiFrameInfoBuilder::UNKNOWN_FRAME_INTERVAL);
+ proxy->forceDrawNextFrame();
proxy->syncAndDrawFrame();
}
@@ -163,6 +163,7 @@ static void doRun(const TestScene::Info& info, const TestScene::Options& opts, i
UiFrameInfoBuilder::UNKNOWN_DEADLINE,
UiFrameInfoBuilder::UNKNOWN_FRAME_INTERVAL);
scene->doFrame(i);
+ proxy->forceDrawNextFrame();
proxy->syncAndDrawFrame();
}
if (opts.reportFrametimeWeight) {
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 955a5e7d8b3a..0c389bfe8b71 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -36,19 +36,16 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) {
EXPECT_EQ(0u, layerUpdater->backingLayer()->getHeight());
EXPECT_FALSE(layerUpdater->backingLayer()->getForceFilter());
EXPECT_FALSE(layerUpdater->backingLayer()->isBlend());
- EXPECT_EQ(Matrix4::identity(), layerUpdater->backingLayer()->getTexTransform());
// push the deferred updates to the layer
- SkMatrix scaledMatrix = SkMatrix::Scale(0.5, 0.5);
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
- layerUpdater->updateLayer(true, scaledMatrix, layerImage);
+ layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
// the backing layer should now have all the properties applied.
EXPECT_EQ(100u, layerUpdater->backingLayer()->getWidth());
EXPECT_EQ(100u, layerUpdater->backingLayer()->getHeight());
EXPECT_TRUE(layerUpdater->backingLayer()->getForceFilter());
EXPECT_TRUE(layerUpdater->backingLayer()->isBlend());
- EXPECT_EQ(scaledMatrix, layerUpdater->backingLayer()->getTexTransform());
}
diff --git a/libs/hwui/tests/unit/FrameMetricsReporterTests.cpp b/libs/hwui/tests/unit/FrameMetricsReporterTests.cpp
new file mode 100644
index 000000000000..571a26707c93
--- /dev/null
+++ b/libs/hwui/tests/unit/FrameMetricsReporterTests.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <FrameMetricsObserver.h>
+#include <FrameMetricsReporter.h>
+#include <utils/TimeUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+using ::testing::NotNull;
+
+class TestFrameMetricsObserver : public FrameMetricsObserver {
+public:
+ explicit TestFrameMetricsObserver(bool waitForPresentTime)
+ : FrameMetricsObserver(waitForPresentTime){};
+
+ MOCK_METHOD(void, notify, (const int64_t* buffer), (override));
+};
+
+// To make sure it is clear that something went wrong if no from frame is set (to make it easier
+// to catch bugs were we forget to set the fromFrame).
+TEST(FrameMetricsReporter, doesNotReportAnyFrameIfNoFromFrameIsSpecified) {
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer = sp<TestFrameMetricsObserver>::make(false /*waitForPresentTime*/);
+ EXPECT_CALL(*observer, notify).Times(0);
+
+ reporter->addObserver(observer.get());
+
+ const int64_t* stats;
+ bool hasPresentTime = false;
+ uint64_t frameNumber = 1;
+ int32_t surfaceControlId = 0;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ frameNumber = 10;
+ surfaceControlId = 0;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ frameNumber = 0;
+ surfaceControlId = 2;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ frameNumber = 10;
+ surfaceControlId = 2;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+}
+
+TEST(FrameMetricsReporter, respectsWaitForPresentTimeUnset) {
+ const int64_t* stats;
+ bool hasPresentTime = false;
+ uint64_t frameNumber = 3;
+ int32_t surfaceControlId = 0;
+
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer = sp<TestFrameMetricsObserver>::make(hasPresentTime);
+ observer->reportMetricsFrom(frameNumber, surfaceControlId);
+ reporter->addObserver(observer.get());
+
+ EXPECT_CALL(*observer, notify).Times(1);
+ hasPresentTime = false;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ EXPECT_CALL(*observer, notify).Times(0);
+ hasPresentTime = true;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+}
+
+TEST(FrameMetricsReporter, respectsWaitForPresentTimeSet) {
+ const int64_t* stats;
+ bool hasPresentTime = true;
+ uint64_t frameNumber = 3;
+ int32_t surfaceControlId = 0;
+
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer = sp<TestFrameMetricsObserver>::make(hasPresentTime);
+ observer->reportMetricsFrom(frameNumber, surfaceControlId);
+ reporter->addObserver(observer.get());
+
+ EXPECT_CALL(*observer, notify).Times(0);
+ hasPresentTime = false;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ EXPECT_CALL(*observer, notify).Times(1);
+ hasPresentTime = true;
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+}
+
+TEST(FrameMetricsReporter, reportsAllFramesAfterSpecifiedFromFrame) {
+ const int64_t* stats;
+ bool hasPresentTime = false;
+
+ std::vector<uint64_t> frameNumbers{0, 1, 10};
+ std::vector<int32_t> surfaceControlIds{0, 1, 10};
+ for (uint64_t frameNumber : frameNumbers) {
+ for (int32_t surfaceControlId : surfaceControlIds) {
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer =
+ sp<TestFrameMetricsObserver>::make(hasPresentTime /*waitForPresentTime*/);
+ observer->reportMetricsFrom(frameNumber, surfaceControlId);
+ reporter->addObserver(observer.get());
+
+ EXPECT_CALL(*observer, notify).Times(8);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 1, surfaceControlId);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 10, surfaceControlId);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId + 1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber - 1,
+ surfaceControlId + 1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 1,
+ surfaceControlId + 1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 10,
+ surfaceControlId + 1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 10,
+ surfaceControlId + 10);
+ }
+ }
+}
+
+TEST(FrameMetricsReporter, doesNotReportsFramesBeforeSpecifiedFromFrame) {
+ const int64_t* stats;
+ bool hasPresentTime = false;
+
+ std::vector<uint64_t> frameNumbers{1, 10};
+ std::vector<int32_t> surfaceControlIds{0, 1, 10};
+ for (uint64_t frameNumber : frameNumbers) {
+ for (int32_t surfaceControlId : surfaceControlIds) {
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer =
+ sp<TestFrameMetricsObserver>::make(hasPresentTime /*waitForPresentTime*/);
+ observer->reportMetricsFrom(frameNumber, surfaceControlId);
+ reporter->addObserver(observer.get());
+
+ EXPECT_CALL(*observer, notify).Times(0);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber - 1, surfaceControlId);
+ if (surfaceControlId > 0) {
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber,
+ surfaceControlId - 1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber - 1,
+ surfaceControlId - 1);
+ }
+ }
+ }
+}
+
+TEST(FrameMetricsReporter, canRemoveObservers) {
+ const int64_t* stats;
+ bool hasPresentTime = false;
+ uint64_t frameNumber = 3;
+ int32_t surfaceControlId = 0;
+
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer = sp<TestFrameMetricsObserver>::make(hasPresentTime /*waitForPresentTime*/);
+
+ observer->reportMetricsFrom(frameNumber, surfaceControlId);
+ reporter->addObserver(observer.get());
+
+ EXPECT_CALL(*observer, notify).Times(1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ ASSERT_TRUE(reporter->removeObserver(observer.get()));
+
+ EXPECT_CALL(*observer, notify).Times(0);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+}
+
+TEST(FrameMetricsReporter, canSupportMultipleObservers) {
+ const int64_t* stats;
+ bool hasPresentTime = false;
+ uint64_t frameNumber = 3;
+ int32_t surfaceControlId = 0;
+
+ auto reporter = std::make_shared<FrameMetricsReporter>();
+
+ auto observer1 = sp<TestFrameMetricsObserver>::make(hasPresentTime /*waitForPresentTime*/);
+ auto observer2 = sp<TestFrameMetricsObserver>::make(hasPresentTime /*waitForPresentTime*/);
+ observer1->reportMetricsFrom(frameNumber, surfaceControlId);
+ observer2->reportMetricsFrom(frameNumber + 10, surfaceControlId + 1);
+ reporter->addObserver(observer1.get());
+ reporter->addObserver(observer2.get());
+
+ EXPECT_CALL(*observer1, notify).Times(1);
+ EXPECT_CALL(*observer2, notify).Times(0);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber, surfaceControlId);
+
+ EXPECT_CALL(*observer1, notify).Times(1);
+ EXPECT_CALL(*observer2, notify).Times(1);
+ reporter->reportFrameMetrics(stats, hasPresentTime, frameNumber + 10, surfaceControlId + 1);
+}
diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp
index f467ebf5d888..5b397de36a86 100644
--- a/libs/hwui/tests/unit/JankTrackerTests.cpp
+++ b/libs/hwui/tests/unit/JankTrackerTests.cpp
@@ -34,6 +34,9 @@ TEST(JankTracker, noJank) {
JankTracker jankTracker(&container);
std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
FrameInfo* info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
info->set(FrameInfoIndex::Vsync) = 101_ms;
@@ -42,7 +45,7 @@ TEST(JankTracker, noJank) {
info->set(FrameInfoIndex::FrameCompleted) = 115_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 116_ms;
@@ -52,7 +55,7 @@ TEST(JankTracker, noJank) {
info->set(FrameInfoIndex::FrameCompleted) = 131_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->totalFrameCount());
ASSERT_EQ(0, container.get()->jankFrameCount());
@@ -65,6 +68,9 @@ TEST(JankTracker, jank) {
JankTracker jankTracker(&container);
std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
FrameInfo* info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
info->set(FrameInfoIndex::Vsync) = 101_ms;
@@ -73,7 +79,7 @@ TEST(JankTracker, jank) {
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->totalFrameCount());
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -85,6 +91,9 @@ TEST(JankTracker, legacyJankButNoRealJank) {
JankTracker jankTracker(&container);
std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
FrameInfo* info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
info->set(FrameInfoIndex::Vsync) = 101_ms;
@@ -93,7 +102,7 @@ TEST(JankTracker, legacyJankButNoRealJank) {
info->set(FrameInfoIndex::FrameCompleted) = 118_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->totalFrameCount());
ASSERT_EQ(0, container.get()->jankFrameCount());
@@ -106,6 +115,9 @@ TEST(JankTracker, doubleStuffed) {
JankTracker jankTracker(&container);
std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
// First frame janks
FrameInfo* info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
@@ -115,7 +127,7 @@ TEST(JankTracker, doubleStuffed) {
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -128,7 +140,7 @@ TEST(JankTracker, doubleStuffed) {
info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->totalFrameCount());
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -140,6 +152,9 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
JankTracker jankTracker(&container);
std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
// First frame janks
FrameInfo* info = jankTracker.startFrame();
info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
@@ -149,7 +164,7 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -162,7 +177,7 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -175,8 +190,8 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
info->set(FrameInfoIndex::FrameCompleted) = 169_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 168_ms;
- jankTracker.finishFrame(*info, reporter);
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(3, container.get()->totalFrameCount());
ASSERT_EQ(2, container.get()->jankFrameCount());
-} \ No newline at end of file
+}
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 423400eb8ff1..ec949b80ea55 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -469,7 +469,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
}
SkCanvas* onNewCanvas() override { return new ProjectionTestCanvas(mDrawCounter); }
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
- void onCopyOnWrite(ContentChangeMode) override {}
+ bool onCopyOnWrite(ContentChangeMode) override { return true; }
int* mDrawCounter;
void onWritePixels(const SkPixmap&, int x, int y) {}
};
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index a1ba70a22581..dc1b2e668dd0 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -61,11 +61,11 @@ TEST(SkiaBehavior, lightingColorFilter_simplify) {
TEST(SkiaBehavior, porterDuffCreateIsCached) {
SkPaint paint;
paint.setBlendMode(SkBlendMode::kOverlay);
- auto expected = paint.getBlendMode();
+ auto expected = paint.asBlendMode();
paint.setBlendMode(SkBlendMode::kClear);
- ASSERT_NE(expected, paint.getBlendMode());
+ ASSERT_NE(expected, paint.asBlendMode());
paint.setBlendMode(SkBlendMode::kOverlay);
- ASSERT_EQ(expected, paint.getBlendMode());
+ ASSERT_EQ(expected, paint.asBlendMode());
}
TEST(SkiaBehavior, pathIntersection) {
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 8c999c41bf7b..60ae6044cd5b 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -221,7 +221,7 @@ public:
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { return nullptr; }
T* canvas() { return static_cast<T*>(getCanvas()); }
- void onCopyOnWrite(ContentChangeMode) override {}
+ bool onCopyOnWrite(ContentChangeMode) override { return true; }
void onWritePixels(const SkPixmap&, int x, int y) override {}
};
}
@@ -382,7 +382,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
std::vector<sp<RenderNode>> nodes;
nodes.push_back(TestUtils::createSkiaNode(
20, 20, 30, 30, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
- canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace_deprecated);
+ canvas.replaceClipRect_deprecated(0, -20, 10, 30);
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
}));
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 5d9f2297c15a..2293ace0bd16 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -57,6 +57,10 @@ static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t
colorType = kRGBA_F16_SkColorType;
alphaType = kPremul_SkAlphaType;
break;
+ case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+ colorType = kAlpha_8_SkColorType;
+ alphaType = kPremul_SkAlphaType;
+ break;
default:
ALOGV("Unsupported format: %d, return unknown by default", format);
break;
@@ -90,6 +94,8 @@ uint32_t ColorTypeToBufferFormat(SkColorType colorType) {
// Hardcoding the value from android::PixelFormat
static constexpr uint64_t kRGBA4444 = 7;
return kRGBA4444;
+ case kAlpha_8_SkColorType:
+ return AHARDWAREBUFFER_FORMAT_R8_UNORM;
default:
ALOGV("Unsupported colorType: %d, return RGBA_8888 by default", (int)colorType);
return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
@@ -219,6 +225,7 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
gamut = SkNamedGamut::kSRGB;
break;
case HAL_DATASPACE_STANDARD_BT2020:
+ case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
gamut = SkNamedGamut::kRec2020;
break;
case HAL_DATASPACE_STANDARD_DCI_P3:
@@ -233,7 +240,6 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
case HAL_DATASPACE_STANDARD_BT601_525:
case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
- case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
case HAL_DATASPACE_STANDARD_BT470M:
case HAL_DATASPACE_STANDARD_FILM:
default:
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index a8f2d9a28d67..94bcb1110e05 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -32,13 +32,6 @@ namespace uirenderer {
*/
class PaintUtils {
public:
- static inline GLenum getFilter(const SkPaint* paint) {
- if (!paint || paint->getFilterQuality() != kNone_SkFilterQuality) {
- return GL_LINEAR;
- }
- return GL_NEAREST;
- }
-
static bool isOpaquePaint(const SkPaint* paint) {
if (!paint) return true; // default (paintless) behavior is SrcOver, black
@@ -48,7 +41,7 @@ public:
}
// Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
- SkBlendMode mode = paint->getBlendMode();
+ const auto mode = paint->asBlendMode();
return mode == SkBlendMode::kSrcOver || mode == SkBlendMode::kSrc;
}
@@ -59,7 +52,7 @@ public:
}
static inline SkBlendMode getBlendModeDirect(const SkPaint* paint) {
- return paint ? paint->getBlendMode() : SkBlendMode::kSrcOver;
+ return paint ? paint->getBlendMode_or(SkBlendMode::kSrcOver) : SkBlendMode::kSrcOver;
}
static inline int getAlphaDirect(const SkPaint* paint) {
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 55f932dffff1..6c0fd5f65359 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -55,6 +55,7 @@ cc_library_shared {
"-Wall",
"-Wextra",
"-Werror",
+ "-Wthread-safety",
],
}
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 8f04cfb70469..1dc74e5f7740 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -17,24 +17,41 @@
#define LOG_TAG "PointerController"
//#define LOG_NDEBUG 0
-// Log debug messages about pointer updates
-#define DEBUG_POINTER_UPDATES 0
-
#include "PointerController.h"
-#include "MouseCursorController.h"
-#include "PointerControllerContext.h"
-#include "TouchSpotController.h"
-
-#include <log/log.h>
-#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
-#include <SkPaint.h>
+#include <android-base/thread_annotations.h>
+
+#include "PointerControllerContext.h"
namespace android {
+namespace {
+
+const ui::Transform kIdentityTransform;
+
+} // namespace
+
+// --- PointerController::DisplayInfoListener ---
+
+void PointerController::DisplayInfoListener::onWindowInfosChanged(
+ const std::vector<android::gui::WindowInfo>&,
+ const std::vector<android::gui::DisplayInfo>& displayInfos) {
+ std::scoped_lock lock(mLock);
+ if (mPointerController == nullptr) return;
+
+ // PointerController uses DisplayInfoListener's lock.
+ base::ScopedLockAssertion assumeLocked(mPointerController->getLock());
+ mPointerController->onDisplayInfosChangedLocked(displayInfos);
+}
+
+void PointerController::DisplayInfoListener::onPointerControllerDestroyed() {
+ std::scoped_lock lock(mLock);
+ mPointerController = nullptr;
+}
+
// --- PointerController ---
std::shared_ptr<PointerController> PointerController::create(
@@ -63,9 +80,36 @@ std::shared_ptr<PointerController> PointerController::create(
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper,
const sp<SpriteController>& spriteController)
- : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
- std::scoped_lock lock(mLock);
+ : PointerController(
+ policy, looper, spriteController,
+ [](const sp<android::gui::WindowInfosListener>& listener) {
+ SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
+ },
+ [](const sp<android::gui::WindowInfosListener>& listener) {
+ SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
+ }) {}
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController,
+ WindowListenerConsumer registerListener,
+ WindowListenerConsumer unregisterListener)
+ : mContext(policy, looper, spriteController, *this),
+ mCursorController(mContext),
+ mDisplayInfoListener(new DisplayInfoListener(this)),
+ mUnregisterWindowInfosListener(std::move(unregisterListener)) {
+ std::scoped_lock lock(getLock());
mLocked.presentation = Presentation::SPOT;
+ registerListener(mDisplayInfoListener);
+}
+
+PointerController::~PointerController() {
+ mDisplayInfoListener->onPointerControllerDestroyed();
+ mUnregisterWindowInfosListener(mDisplayInfoListener);
+}
+
+std::mutex& PointerController::getLock() const {
+ return mDisplayInfoListener->mLock;
}
bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
@@ -74,7 +118,14 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX
}
void PointerController::move(float deltaX, float deltaY) {
- mCursorController.move(deltaX, deltaY);
+ const int32_t displayId = mCursorController.getDisplayId();
+ vec2 transformed;
+ {
+ std::scoped_lock lock(getLock());
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+ }
+ mCursorController.move(transformed.x, transformed.y);
}
void PointerController::setButtonState(int32_t buttonState) {
@@ -86,12 +137,26 @@ int32_t PointerController::getButtonState() const {
}
void PointerController::setPosition(float x, float y) {
- std::scoped_lock lock(mLock);
- mCursorController.setPosition(x, y);
+ const int32_t displayId = mCursorController.getDisplayId();
+ vec2 transformed;
+ {
+ std::scoped_lock lock(getLock());
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ transformed = transform.transform(x, y);
+ }
+ mCursorController.setPosition(transformed.x, transformed.y);
}
void PointerController::getPosition(float* outX, float* outY) const {
+ const int32_t displayId = mCursorController.getDisplayId();
mCursorController.getPosition(outX, outY);
+ {
+ std::scoped_lock lock(getLock());
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ const auto xy = transform.inverse().transform(*outX, *outY);
+ *outX = xy.x;
+ *outY = xy.y;
+ }
}
int32_t PointerController::getDisplayId() const {
@@ -99,17 +164,17 @@ int32_t PointerController::getDisplayId() const {
}
void PointerController::fade(Transition transition) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
mCursorController.fade(transition);
}
void PointerController::unfade(Transition transition) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
mCursorController.unfade(transition);
}
void PointerController::setPresentation(Presentation presentation) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
if (mLocked.presentation == presentation) {
return;
@@ -129,20 +194,34 @@ void PointerController::setPresentation(Presentation presentation) {
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
+ std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
+ const ui::Transform& transform = getTransformForDisplayLocked(displayId);
+
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
+
+ const vec2 xy = transform.transform(spotCoords[index].getXYValue());
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+ float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ }
+
auto it = mLocked.spotControllers.find(displayId);
if (it == mLocked.spotControllers.end()) {
mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
}
- mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
+ mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
}
void PointerController::clearSpots() {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
clearSpotsLocked();
}
-void PointerController::clearSpotsLocked() REQUIRES(mLock) {
+void PointerController::clearSpotsLocked() {
for (auto& [displayID, spotController] : mLocked.spotControllers) {
spotController.clearSpots();
}
@@ -153,7 +232,7 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout
}
void PointerController::reloadPointerResources() {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
for (auto& [displayID, spotController] : mLocked.spotControllers) {
spotController.reloadSpotResources();
@@ -169,7 +248,7 @@ void PointerController::reloadPointerResources() {
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
bool getAdditionalMouseResources = false;
if (mLocked.presentation == PointerController::Presentation::POINTER) {
@@ -179,12 +258,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
}
void PointerController::updatePointerIcon(int32_t iconId) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
mCursorController.updatePointerIcon(iconId);
}
void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
mCursorController.setCustomPointerIcon(icon);
}
@@ -194,11 +273,11 @@ void PointerController::doInactivityTimeout() {
void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
std::unordered_set<int32_t> displayIdSet;
- for (DisplayViewport viewport : viewports) {
+ for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
}
- std::scoped_lock lock(mLock);
+ std::scoped_lock lock(getLock());
for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
int32_t displayID = it->first;
if (!displayIdSet.count(displayID)) {
@@ -214,4 +293,17 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>&
}
}
+void PointerController::onDisplayInfosChangedLocked(
+ const std::vector<gui::DisplayInfo>& displayInfo) {
+ mLocked.mDisplayInfos = displayInfo;
+}
+
+const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+ const auto& di = mLocked.mDisplayInfos;
+ auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
+ return info.displayId == displayId;
+ });
+ return it != di.end() ? it->transform : kIdentityTransform;
+}
+
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 97567bab202b..2e6e851ee15a 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@ public:
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController);
- virtual ~PointerController() = default;
+ ~PointerController() override;
virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
virtual void move(float deltaX, float deltaY);
@@ -72,11 +72,31 @@ public:
void reloadPointerResources();
void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
+ void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+ REQUIRES(getLock());
+
+protected:
+ using WindowListenerConsumer =
+ std::function<void(const sp<android::gui::WindowInfosListener>&)>;
+
+ // Constructor used to test WindowInfosListener registration.
+ PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController,
+ WindowListenerConsumer registerListener,
+ WindowListenerConsumer unregisterListener);
+
private:
+ PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController);
+
friend PointerControllerContext::LooperCallback;
friend PointerControllerContext::MessageHandler;
- mutable std::mutex mLock;
+ // PointerController's DisplayInfoListener can outlive the PointerController because when the
+ // listener is registered, a strong pointer to the listener (which can extend its lifecycle)
+ // is given away. To avoid the small overhead of using two separate locks in these two objects,
+ // we use the DisplayInfoListener's lock in PointerController.
+ std::mutex& getLock() const;
PointerControllerContext mContext;
@@ -85,12 +105,30 @@ private:
struct Locked {
Presentation presentation;
+ std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
- } mLocked GUARDED_BY(mLock);
+ } mLocked GUARDED_BY(getLock());
- PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController);
- void clearSpotsLocked();
+ class DisplayInfoListener : public gui::WindowInfosListener {
+ public:
+ explicit DisplayInfoListener(PointerController* pc) : mPointerController(pc){};
+ void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&,
+ const std::vector<android::gui::DisplayInfo>&) override;
+ void onPointerControllerDestroyed();
+
+ // This lock is also used by PointerController. See PointerController::getLock().
+ std::mutex mLock;
+
+ private:
+ PointerController* mPointerController GUARDED_BY(mLock);
+ };
+
+ sp<DisplayInfoListener> mDisplayInfoListener;
+ const WindowListenerConsumer mUnregisterWindowInfosListener;
+
+ const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
+
+ void clearSpotsLocked() REQUIRES(getLock());
};
} // namespace android
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index d10e68816d28..2b809eab4ae4 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -27,10 +27,12 @@ namespace android {
// --- SpriteController ---
-SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
- mLooper(looper), mOverlayLayer(overlayLayer) {
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer,
+ ParentSurfaceProvider parentSurfaceProvider)
+ : mLooper(looper),
+ mOverlayLayer(overlayLayer),
+ mParentSurfaceProvider(std::move(parentSurfaceProvider)) {
mHandler = new WeakMessageHandler(this);
-
mLocked.transactionNestingCount = 0;
mLocked.deferredSpriteUpdate = false;
}
@@ -68,8 +70,8 @@ void SpriteController::closeTransaction() {
}
void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
- bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
- mLocked.invalidatedSprites.push(sprite);
+ bool wasEmpty = mLocked.invalidatedSprites.empty();
+ mLocked.invalidatedSprites.push_back(sprite);
if (wasEmpty) {
if (mLocked.transactionNestingCount != 0) {
mLocked.deferredSpriteUpdate = true;
@@ -80,8 +82,8 @@ void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
}
void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
- bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
- mLocked.disposedSurfaces.push(surfaceControl);
+ bool wasEmpty = mLocked.disposedSurfaces.empty();
+ mLocked.disposedSurfaces.push_back(surfaceControl);
if (wasEmpty) {
mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
}
@@ -111,7 +113,7 @@ void SpriteController::doUpdateSprites() {
numSprites = mLocked.invalidatedSprites.size();
for (size_t i = 0; i < numSprites; i++) {
- const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites[i];
updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
sprite->resetDirtyLocked();
@@ -168,7 +170,7 @@ void SpriteController::doUpdateSprites() {
// If surface is a new one, we have to set right layer stack.
if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
- t.setLayerStack(update.state.surfaceControl, update.state.displayId);
+ t.reparent(update.state.surfaceControl, mParentSurfaceProvider(update.state.displayId));
needApplyTransaction = true;
}
}
@@ -303,7 +305,7 @@ void SpriteController::doUpdateSprites() {
void SpriteController::doDisposeSurfaces() {
// Collect disposed surfaces.
- Vector<sp<SurfaceControl> > disposedSurfaces;
+ std::vector<sp<SurfaceControl>> disposedSurfaces;
{ // acquire lock
AutoMutex _l(mLock);
@@ -311,6 +313,13 @@ void SpriteController::doDisposeSurfaces() {
mLocked.disposedSurfaces.clear();
} // release lock
+ // Remove the parent from all surfaces.
+ SurfaceComposerClient::Transaction t;
+ for (const sp<SurfaceControl>& sc : disposedSurfaces) {
+ t.reparent(sc, nullptr);
+ }
+ t.apply();
+
// Release the last reference to each surface outside of the lock.
// We don't want the surfaces to be deleted while we are holding our lock.
disposedSurfaces.clear();
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 137b5646feae..2e9cb9685c46 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -114,7 +114,8 @@ protected:
virtual ~SpriteController();
public:
- SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+ using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
+ SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
/* Creates a new sprite, initially invisible. */
virtual sp<Sprite> createSprite();
@@ -245,12 +246,13 @@ private:
sp<Looper> mLooper;
const int32_t mOverlayLayer;
sp<WeakMessageHandler> mHandler;
+ ParentSurfaceProvider mParentSurfaceProvider;
sp<SurfaceComposerClient> mSurfaceComposerClient;
struct Locked {
- Vector<sp<SpriteImpl> > invalidatedSprites;
- Vector<sp<SurfaceControl> > disposedSurfaces;
+ std::vector<sp<SpriteImpl>> invalidatedSprites;
+ std::vector<sp<SurfaceControl>> disposedSurfaces;
uint32_t transactionNestingCount;
bool deferredSpriteUpdate;
} mLocked; // guarded by mLock
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index b67088a389b6..dae1fccec804 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -255,4 +255,36 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
ensureDisplayViewportIsSet();
}
+class PointerControllerWindowInfoListenerTest : public Test {};
+
+class TestPointerController : public PointerController {
+public:
+ TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
+ const sp<Looper>& looper)
+ : PointerController(
+ new MockPointerControllerPolicyInterface(), looper,
+ new NiceMock<MockSpriteController>(looper),
+ [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Register listener
+ registeredListener = listener;
+ },
+ [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Unregister listener
+ if (registeredListener == listener) registeredListener = nullptr;
+ }) {}
+};
+
+TEST_F(PointerControllerWindowInfoListenerTest,
+ doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
+ sp<android::gui::WindowInfosListener> registeredListener;
+ sp<android::gui::WindowInfosListener> localListenerCopy;
+ {
+ TestPointerController pointerController(registeredListener, new Looper(false));
+ ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
+ localListenerCopy = registeredListener;
+ }
+ EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
+ localListenerCopy->onWindowInfosChanged({}, {});
+}
+
} // namespace android
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
index a034f66c9abf..62f1d65e77a5 100644
--- a/libs/input/tests/mocks/MockSpriteController.h
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -26,7 +26,8 @@ namespace android {
class MockSpriteController : public SpriteController {
public:
- MockSpriteController(sp<Looper> looper) : SpriteController(looper, 0) {}
+ MockSpriteController(sp<Looper> looper)
+ : SpriteController(looper, 0, [](int) { return nullptr; }) {}
~MockSpriteController() {}
MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index bf2e764aae6a..f656ebfc3b77 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -27,6 +27,7 @@ cc_library_shared {
name: "libservices",
srcs: [
":IDropBoxManagerService.aidl",
+ ":ILogcatManagerService_aidl",
"src/content/ComponentName.cpp",
"src/os/DropBoxManager.cpp",
],